Преглед изворни кода

Add spring-security-release-plugin

Issue gh-14242
Steve Riesenberg пре 2 година
родитељ
комит
ad71f57143
44 измењених фајлова са 10 додато и 5209 уклоњено
  1. 5 50
      build.gradle
  2. 4 25
      buildSrc/build.gradle
  3. 0 71
      buildSrc/src/main/java/org/springframework/gradle/antora/AntoraVersionPlugin.java
  4. 0 55
      buildSrc/src/main/java/org/springframework/gradle/antora/AntoraVersionUtils.java
  5. 0 96
      buildSrc/src/main/java/org/springframework/gradle/antora/CheckAntoraVersionTask.java
  6. 0 138
      buildSrc/src/main/java/org/springframework/gradle/antora/UpdateAntoraVersionTask.java
  7. 0 70
      buildSrc/src/main/java/org/springframework/gradle/github/RepositoryRef.java
  8. 0 103
      buildSrc/src/main/java/org/springframework/gradle/github/changelog/GitHubChangelogPlugin.java
  9. 0 271
      buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneApi.java
  10. 0 110
      buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneHasNoOpenIssuesTask.java
  11. 0 93
      buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneNextReleaseTask.java
  12. 0 102
      buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneNextVersionDueTodayTask.java
  13. 0 73
      buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestonePlugin.java
  14. 0 23
      buildSrc/src/main/java/org/springframework/gradle/github/milestones/LocalDateAdapter.java
  15. 0 25
      buildSrc/src/main/java/org/springframework/gradle/github/milestones/LocalDateTimeAdapter.java
  16. 0 67
      buildSrc/src/main/java/org/springframework/gradle/github/milestones/Milestone.java
  17. 0 29
      buildSrc/src/main/java/org/springframework/gradle/github/milestones/NextVersionYml.java
  18. 0 147
      buildSrc/src/main/java/org/springframework/gradle/github/milestones/ScheduleNextReleaseTask.java
  19. 0 136
      buildSrc/src/main/java/org/springframework/gradle/github/milestones/SpringReleaseTrain.java
  20. 0 205
      buildSrc/src/main/java/org/springframework/gradle/github/milestones/SpringReleaseTrainSpec.java
  21. 0 130
      buildSrc/src/main/java/org/springframework/gradle/github/release/CreateGitHubReleaseTask.java
  22. 0 84
      buildSrc/src/main/java/org/springframework/gradle/github/release/DispatchGitHubWorkflowTask.java
  23. 0 98
      buildSrc/src/main/java/org/springframework/gradle/github/release/GitHubActionsApi.java
  24. 0 91
      buildSrc/src/main/java/org/springframework/gradle/github/release/GitHubReleaseApi.java
  25. 0 54
      buildSrc/src/main/java/org/springframework/gradle/github/release/GitHubReleasePlugin.java
  26. 0 156
      buildSrc/src/main/java/org/springframework/gradle/github/release/Release.java
  27. 0 51
      buildSrc/src/main/java/org/springframework/gradle/github/release/WorkflowDispatch.java
  28. 0 82
      buildSrc/src/main/java/org/springframework/gradle/github/user/GitHubUserApi.java
  29. 0 53
      buildSrc/src/main/java/org/springframework/gradle/github/user/User.java
  30. 0 123
      buildSrc/src/main/java/org/springframework/gradle/sagan/Release.java
  31. 0 93
      buildSrc/src/main/java/org/springframework/gradle/sagan/SaganApi.java
  32. 0 109
      buildSrc/src/main/java/org/springframework/gradle/sagan/SaganCreateReleaseTask.java
  33. 0 68
      buildSrc/src/main/java/org/springframework/gradle/sagan/SaganDeleteReleaseTask.java
  34. 0 47
      buildSrc/src/main/java/org/springframework/gradle/sagan/SaganPlugin.java
  35. 0 44
      buildSrc/src/main/java/org/springframework/security/convention/versions/UpdateProjectVersionPlugin.java
  36. 0 63
      buildSrc/src/main/java/org/springframework/security/convention/versions/UpdateProjectVersionTask.java
  37. 0 68
      buildSrc/src/main/java/org/springframework/security/convention/versions/UpdateToSnapshotVersionTask.java
  38. 0 85
      buildSrc/src/test/java/io/spring/gradle/convention/sagan/SaganApiTests.java
  39. 0 1226
      buildSrc/src/test/java/org/springframework/gradle/github/milestones/GitHubMilestoneApiTests.java
  40. 0 245
      buildSrc/src/test/java/org/springframework/gradle/github/milestones/SpringReleaseTrainTests.java
  41. 0 89
      buildSrc/src/test/java/org/springframework/gradle/github/release/GitHubActionsApiTests.java
  42. 0 155
      buildSrc/src/test/java/org/springframework/gradle/github/release/GitHubReleaseApiTests.java
  43. 0 106
      buildSrc/src/test/java/org/springframework/gradle/github/user/GitHubUserApiTests.java
  44. 1 0
      gradle/libs.versions.toml

+ 5 - 50
build.gradle

@@ -19,12 +19,8 @@ apply plugin: 'locks'
 apply plugin: 's101'
 apply plugin: 'io.spring.convention.root'
 apply plugin: 'org.jetbrains.kotlin.jvm'
-apply plugin: 'org.springframework.security.update-version'
-apply plugin: 'org.springframework.security.sagan'
-apply plugin: 'org.springframework.github.milestone'
-apply plugin: 'org.springframework.github.changelog'
-apply plugin: 'org.springframework.github.release'
 apply plugin: 'org.springframework.security.versions.verify-dependencies-versions'
+apply plugin: 'io.spring.security.release'
 
 group = 'org.springframework.security'
 description = 'Spring Security'
@@ -37,53 +33,12 @@ repositories {
 	mavenCentral()
 }
 
-tasks.named("saganCreateRelease") {
-	referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html"
-	apiDocUrl = "https://docs.spring.io/spring-security/site/docs/{version}/api/"
-}
-
-tasks.named("gitHubCheckMilestoneHasNoOpenIssues") {
-	repository {
-		owner = "spring-projects"
-		name = "spring-security"
-	}
-}
-
-tasks.named("gitHubNextReleaseMilestone") {
-	repository {
-		owner = "spring-projects"
-		name = "spring-security"
-	}
-}
-
-tasks.named("gitHubCheckNextVersionDueToday") {
-	repository {
-		owner = "spring-projects"
-		name = "spring-security"
-	}
-}
-
-tasks.named("scheduleNextRelease") {
-	repository {
-		owner = "spring-projects"
-		name = "spring-security"
-	}
+springRelease {
 	weekOfMonth = 3
 	dayOfWeek = 1
-}
-
-tasks.named("createGitHubRelease") {
-	repository {
-		owner = "spring-projects"
-		name = "spring-security"
-	}
-}
-
-tasks.named("dispatchGitHubWorkflow") {
-	repository {
-		owner = "spring-projects"
-		name = "spring-security"
-	}
+	referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html"
+	apiDocUrl = "https://docs.spring.io/spring-security/docs/{version}/api/"
+	replaceSnapshotVersionInReferenceDocUrl = true
 }
 
 subprojects {

+ 4 - 25
buildSrc/build.gradle

@@ -26,10 +26,6 @@ sourceSets {
 
 gradlePlugin {
 	plugins {
-		checkAntoraVersion {
-			id = "org.springframework.antora.check-version"
-			implementationClass = "org.springframework.gradle.antora.AntoraVersionPlugin"
-		}
 		trang {
 			id = "trang"
 			implementationClass = "trang.TrangPlugin"
@@ -42,26 +38,6 @@ gradlePlugin {
 			id = "io.spring.convention.management-configuration"
 			implementationClass = "io.spring.gradle.convention.ManagementConfigurationPlugin"
 		}
-		updateProjectVersion {
-			id = "org.springframework.security.update-version"
-			implementationClass = "org.springframework.security.convention.versions.UpdateProjectVersionPlugin"
-		}
-		sagan {
-			id = "org.springframework.security.sagan"
-			implementationClass = "org.springframework.gradle.sagan.SaganPlugin"
-		}
-		githubMilestone {
-			id = "org.springframework.github.milestone"
-			implementationClass = "org.springframework.gradle.github.milestones.GitHubMilestonePlugin"
-		}
-		githubChangelog {
-			id = "org.springframework.github.changelog"
-			implementationClass = "org.springframework.gradle.github.changelog.GitHubChangelogPlugin"
-		}
-		githubRelease {
-			id = "org.springframework.github.release"
-			implementationClass = "org.springframework.gradle.github.release.GitHubReleasePlugin"
-		}
 		s101 {
 			id = "s101"
 			implementationClass = "s101.S101Plugin"
@@ -95,11 +71,14 @@ dependencies {
 	implementation libs.com.github.spullara.mustache.java.compiler
 	implementation libs.io.spring.javaformat.spring.javaformat.gradle.plugin
 	implementation libs.io.spring.nohttp.nohttp.gradle
-	implementation libs.net.sourceforge.htmlunit
+	implementation (libs.net.sourceforge.htmlunit) {
+		exclude group: 'org.eclipse.jetty.websocket', module: 'websocket-client'
+	}
 	implementation libs.org.hidetake.gradle.ssh.plugin
 	implementation libs.org.jfrog.buildinfo.build.info.extractor.gradle
 	implementation libs.org.sonarsource.scanner.gradle.sonarqube.gradle.plugin
 	implementation libs.com.squareup.okhttp3.okhttp
+	implementation libs.io.spring.security.release.plugin
 
 	testImplementation platform(libs.org.junit.junit.bom)
 	testImplementation platform(libs.org.mockito.mockito.bom)

+ 0 - 71
buildSrc/src/main/java/org/springframework/gradle/antora/AntoraVersionPlugin.java

@@ -1,71 +0,0 @@
-package org.springframework.gradle.antora;
-
-import org.gradle.api.Action;
-import org.gradle.api.GradleException;
-import org.gradle.api.Plugin;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.tasks.TaskProvider;
-import org.gradle.language.base.plugins.LifecycleBasePlugin;
-
-public class AntoraVersionPlugin implements Plugin<Project> {
-	public static final String ANTORA_CHECK_VERSION_TASK_NAME = "antoraCheckVersion";
-
-	@Override
-	public void apply(Project project) {
-		TaskProvider<CheckAntoraVersionTask> antoraCheckVersion = project.getTasks().register(ANTORA_CHECK_VERSION_TASK_NAME, CheckAntoraVersionTask.class, new Action<CheckAntoraVersionTask>() {
-			@Override
-			public void execute(CheckAntoraVersionTask antoraCheckVersion) {
-				antoraCheckVersion.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
-				antoraCheckVersion.setDescription("Checks the antora.yml version properties match the Gradle version");
-				antoraCheckVersion.getAntoraVersion().convention(project.provider(() -> getDefaultAntoraVersion(project)));
-				antoraCheckVersion.getAntoraPrerelease().convention(project.provider(() -> getDefaultAntoraPrerelease(project)));
-				antoraCheckVersion.getAntoraDisplayVersion().convention(project.provider(() -> getDefaultAntoraDisplayVersion(project)));
-				antoraCheckVersion.getAntoraYmlFile().fileProvider(project.provider(() -> project.file("antora.yml")));
-			}
-		});
-		project.getPlugins().withType(LifecycleBasePlugin.class, new Action<LifecycleBasePlugin>() {
-			@Override
-			public void execute(LifecycleBasePlugin lifecycleBasePlugin) {
-				project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME).configure(new Action<Task>() {
-					@Override
-					public void execute(Task check) {
-						check.dependsOn(antoraCheckVersion);
-					}
-				});
-			}
-		});
-		 project.getTasks().register("antoraUpdateVersion", UpdateAntoraVersionTask.class, new Action<UpdateAntoraVersionTask>() {
-			@Override
-			public void execute(UpdateAntoraVersionTask antoraUpdateVersion) {
-				antoraUpdateVersion.setGroup("Release");
-				antoraUpdateVersion.setDescription("Updates the antora.yml version properties to match the Gradle version");
-				antoraUpdateVersion.getAntoraYmlFile().fileProvider(project.provider(() -> project.file("antora.yml")));
-			}
-		});
-	}
-
-	private static String getDefaultAntoraVersion(Project project) {
-		String projectVersion = getProjectVersion(project);
-		return AntoraVersionUtils.getDefaultAntoraVersion(projectVersion);
-	}
-
-	private static String getDefaultAntoraPrerelease(Project project) {
-		String projectVersion = getProjectVersion(project);
-		return AntoraVersionUtils.getDefaultAntoraPrerelease(projectVersion);
-	}
-
-	private static String getDefaultAntoraDisplayVersion(Project project) {
-		String projectVersion = getProjectVersion(project);
-		return AntoraVersionUtils.getDefaultAntoraDisplayVersion(projectVersion);
-	}
-
-	private static String getProjectVersion(Project project) {
-		Object projectVersion = project.getVersion();
-		if (projectVersion == null) {
-			throw new GradleException("Please define antoraVersion and antoraPrerelease on " + ANTORA_CHECK_VERSION_TASK_NAME + " or provide a Project version so they can be defaulted");
-		}
-		return String.valueOf(projectVersion);
-	}
-
-}

+ 0 - 55
buildSrc/src/main/java/org/springframework/gradle/antora/AntoraVersionUtils.java

@@ -1,55 +0,0 @@
-/*
- * Copyright 2019-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.gradle.antora;
-
-public class AntoraVersionUtils {
-
-	public static String getDefaultAntoraVersion(String projectVersion) {
-		int preReleaseIndex = getSnapshotIndex(projectVersion);
-		return isSnapshot(projectVersion) ? projectVersion.substring(0, preReleaseIndex) : projectVersion;
-	}
-
-	public static String getDefaultAntoraPrerelease(String projectVersion) {
-		if (isSnapshot(projectVersion)) {
-			int preReleaseIndex = getSnapshotIndex(projectVersion);
-			return projectVersion.substring(preReleaseIndex);
-		}
-		if (isPreRelease(projectVersion)) {
-			return Boolean.TRUE.toString();
-		}
-		return null;
-	}
-
-	public static String getDefaultAntoraDisplayVersion(String projectVersion) {
-		if (!isSnapshot(projectVersion) && isPreRelease(projectVersion)) {
-			return getDefaultAntoraVersion(projectVersion);
-		}
-		return null;
-	}
-
-	private static boolean isSnapshot(String projectVersion) {
-		return getSnapshotIndex(projectVersion) >= 0;
-	}
-
-	private static int getSnapshotIndex(String projectVersion) {
-		return projectVersion.lastIndexOf("-SNAPSHOT");
-	}
-
-	private static boolean isPreRelease(String projectVersion) {
-		return projectVersion.lastIndexOf("-") >= 0;
-	}
-}

+ 0 - 96
buildSrc/src/main/java/org/springframework/gradle/antora/CheckAntoraVersionTask.java

@@ -1,96 +0,0 @@
-package org.springframework.gradle.antora;
-
-import org.gradle.api.DefaultTask;
-import org.gradle.api.GradleException;
-import org.gradle.api.file.RegularFileProperty;
-import org.gradle.api.provider.Property;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.TaskAction;
-import org.yaml.snakeyaml.Yaml;
-import org.yaml.snakeyaml.constructor.Constructor;
-import org.yaml.snakeyaml.representer.Representer;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-
-public abstract class CheckAntoraVersionTask extends DefaultTask {
-
-	@TaskAction
-	public void check() throws FileNotFoundException {
-		File antoraYmlFile = getAntoraYmlFile().getAsFile().get();
-		String expectedAntoraVersion = getAntoraVersion().get();
-		String expectedAntoraPrerelease = getAntoraPrerelease().getOrElse(null);
-		String expectedAntoraDisplayVersion = getAntoraDisplayVersion().getOrElse(null);
-
-		Representer representer = new Representer();
-		representer.getPropertyUtils().setSkipMissingProperties(true);
-
-		Yaml yaml = new Yaml(new Constructor(AntoraYml.class), representer);
-		AntoraYml antoraYml = yaml.load(new FileInputStream(antoraYmlFile));
-
-		String actualAntoraPrerelease = antoraYml.getPrerelease();
-		boolean preReleaseMatches = antoraYml.getPrerelease() == null && expectedAntoraPrerelease == null ||
-				(actualAntoraPrerelease != null && actualAntoraPrerelease.equals(expectedAntoraPrerelease));
-		String actualAntoraDisplayVersion = antoraYml.getDisplay_version();
-		boolean displayVersionMatches = antoraYml.getDisplay_version() == null && expectedAntoraDisplayVersion == null ||
-				(actualAntoraDisplayVersion != null && actualAntoraDisplayVersion.equals(expectedAntoraDisplayVersion));
-		String actualAntoraVersion = antoraYml.getVersion();
-		if (!preReleaseMatches ||
-				!displayVersionMatches ||
-				!expectedAntoraVersion.equals(actualAntoraVersion)) {
-			throw new GradleException("The Gradle version of '" + getProject().getVersion() + "' should have version: '"
-					+ expectedAntoraVersion + "' prerelease: '" + expectedAntoraPrerelease + "' display_version: '"
-					+ expectedAntoraDisplayVersion + "' defined in " + antoraYmlFile + " but got version: '"
-					+ actualAntoraVersion + "' prerelease: '" + actualAntoraPrerelease + "' display_version: '" + actualAntoraDisplayVersion + "'");
-		}
-	}
-
-	@InputFile
-	public abstract RegularFileProperty getAntoraYmlFile();
-
-	@Input
-	public abstract Property<String> getAntoraVersion();
-
-	@Input
-	@Optional
-	public abstract Property<String> getAntoraPrerelease();
-
-	@Input
-	@Optional
-	public abstract Property<String> getAntoraDisplayVersion();
-
-	public static class AntoraYml {
-		private String version;
-
-		private String prerelease;
-
-		private String display_version;
-
-		public String getVersion() {
-			return version;
-		}
-
-		public void setVersion(String version) {
-			this.version = version;
-		}
-
-		public String getPrerelease() {
-			return prerelease;
-		}
-
-		public void setPrerelease(String prerelease) {
-			this.prerelease = prerelease;
-		}
-
-		public String getDisplay_version() {
-			return display_version;
-		}
-
-		public void setDisplay_version(String display_version) {
-			this.display_version = display_version;
-		}
-	}
-}

+ 0 - 138
buildSrc/src/main/java/org/springframework/gradle/antora/UpdateAntoraVersionTask.java

@@ -1,138 +0,0 @@
-/*
- * Copyright 2019-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.gradle.antora;
-
-import org.gradle.api.DefaultTask;
-import org.gradle.api.GradleException;
-import org.gradle.api.Project;
-import org.gradle.api.file.RegularFileProperty;
-import org.gradle.api.provider.Property;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.TaskAction;
-import org.yaml.snakeyaml.DumperOptions;
-import org.yaml.snakeyaml.Yaml;
-import org.yaml.snakeyaml.constructor.Constructor;
-import org.yaml.snakeyaml.nodes.NodeTuple;
-import org.yaml.snakeyaml.nodes.Tag;
-import org.yaml.snakeyaml.representer.Representer;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileWriter;
-import java.io.IOException;
-
-import org.springframework.gradle.github.milestones.NextVersionYml;
-
-public abstract class UpdateAntoraVersionTask extends DefaultTask {
-
-	@TaskAction
-	public void update() throws IOException {
-		String projectVersion = getProject().getVersion().toString();
-		File antoraYmlFile = getAntoraYmlFile().getAsFile().get();
-		String updatedAntoraVersion = AntoraVersionUtils.getDefaultAntoraVersion(projectVersion);
-		String updatedAntoraPrerelease = AntoraVersionUtils.getDefaultAntoraPrerelease(projectVersion);
-		String updatedAntoraDisplayVersion = AntoraVersionUtils.getDefaultAntoraDisplayVersion(projectVersion);
-
-		Representer representer = new Representer();
-		representer.getPropertyUtils().setSkipMissingProperties(true);
-
-		Yaml yaml = new Yaml(new Constructor(AntoraYml.class), representer);
-		AntoraYml antoraYml = yaml.load(new FileInputStream(antoraYmlFile));
-
-		System.out.println("Updating the version parameters in " + antoraYmlFile.getName() + " to version: "
-				+ updatedAntoraVersion + ", prerelease: " + updatedAntoraPrerelease + ", display_version: "
-				+ updatedAntoraDisplayVersion);
-		antoraYml.setVersion(updatedAntoraVersion);
-		antoraYml.setPrerelease(updatedAntoraPrerelease);
-		antoraYml.setDisplay_version(updatedAntoraDisplayVersion);
-
-		FileWriter outputWriter = new FileWriter(antoraYmlFile);
-		getYaml().dump(antoraYml, outputWriter);
-	}
-
-	@InputFile
-	public abstract RegularFileProperty getAntoraYmlFile();
-
-	public static class AntoraYml {
-
-		private String name;
-
-		private String version;
-
-		private String prerelease;
-
-		private String display_version;
-
-		public String getName() {
-			return name;
-		}
-
-		public void setName(String name) {
-			this.name = name;
-		}
-
-		public String getVersion() {
-			return version;
-		}
-
-		public void setVersion(String version) {
-			this.version = version;
-		}
-
-		public String getPrerelease() {
-			return prerelease;
-		}
-
-		public void setPrerelease(String prerelease) {
-			this.prerelease = prerelease;
-		}
-
-		public String getDisplay_version() {
-			return display_version;
-		}
-
-		public void setDisplay_version(String display_version) {
-			this.display_version = display_version;
-		}
-
-	}
-
-	private Yaml getYaml() {
-		Representer representer = new Representer() {
-			@Override
-			protected NodeTuple representJavaBeanProperty(Object javaBean,
-					org.yaml.snakeyaml.introspector.Property property, Object propertyValue, Tag customTag) {
-				// Don't write out null values
-				if (propertyValue == null) {
-					return null;
-				}
-				else {
-					return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag);
-				}
-			}
-		};
-		representer.addClassTag(AntoraYml.class, Tag.MAP);
-		DumperOptions ymlOptions = new DumperOptions();
-		ymlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
-		ymlOptions.setDefaultScalarStyle(DumperOptions.ScalarStyle.SINGLE_QUOTED);
-		return new Yaml(representer, ymlOptions);
-	}
-
-}

+ 0 - 70
buildSrc/src/main/java/org/springframework/gradle/github/RepositoryRef.java

@@ -1,70 +0,0 @@
-package org.springframework.gradle.github;
-
-import java.io.Serializable;
-
-public class RepositoryRef implements Serializable {
-
-	private static final long serialVersionUID = 7151218536746822797L;
-
-	private String owner;
-
-	private String name;
-
-	public RepositoryRef() {
-	}
-
-	public RepositoryRef(String owner, String name) {
-		this.owner = owner;
-		this.name = name;
-	}
-
-	public String getOwner() {
-		return owner;
-	}
-
-	public void setOwner(String owner) {
-		this.owner = owner;
-	}
-
-	public String getName() {
-		return name;
-	}
-
-	public void setName(String name) {
-		this.name = name;
-	}
-
-	@Override
-	public String toString() {
-		return "RepositoryRef{" +
-				"owner='" + owner + '\'' +
-				", name='" + name + '\'' +
-				'}';
-	}
-
-	public static RepositoryRefBuilder owner(String owner) {
-		return new RepositoryRefBuilder().owner(owner);
-	}
-
-	public static final class RepositoryRefBuilder {
-		private String owner;
-		private String repository;
-
-		private RepositoryRefBuilder() {
-		}
-
-		private RepositoryRefBuilder owner(String owner) {
-			this.owner = owner;
-			return this;
-		}
-
-		public RepositoryRefBuilder repository(String repository) {
-			this.repository = repository;
-			return this;
-		}
-
-		public RepositoryRef build() {
-			return new RepositoryRef(owner, repository);
-		}
-	}
-}

+ 0 - 103
buildSrc/src/main/java/org/springframework/gradle/github/changelog/GitHubChangelogPlugin.java

@@ -1,103 +0,0 @@
-/*
- * Copyright 2019-2020 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.gradle.github.changelog;
-
-import java.io.File;
-import java.nio.file.Paths;
-
-import org.gradle.api.Action;
-import org.gradle.api.Plugin;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.DependencySet;
-import org.gradle.api.artifacts.repositories.ExclusiveContentRepository;
-import org.gradle.api.artifacts.repositories.InclusiveRepositoryContentDescriptor;
-import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
-import org.gradle.api.artifacts.repositories.IvyPatternRepositoryLayout;
-import org.gradle.api.tasks.JavaExec;
-
-public class GitHubChangelogPlugin implements Plugin<Project> {
-
-	public static final String CHANGELOG_GENERATOR_CONFIGURATION_NAME = "changelogGenerator";
-	public static final String RELEASE_NOTES_PATH = "changelog/release-notes.md";
-
-	@Override
-	public void apply(Project project) {
-		createRepository(project);
-		createChangelogGeneratorConfiguration(project);
-		project.getTasks().register("generateChangelog", JavaExec.class, new Action<JavaExec>() {
-			@Override
-			public void execute(JavaExec generateChangelog) {
-				File outputFile = project.file(Paths.get(project.getBuildDir().getPath(), RELEASE_NOTES_PATH));
-				outputFile.getParentFile().mkdirs();
-				generateChangelog.setGroup("Release");
-				generateChangelog.setDescription("Generates the changelog");
-				generateChangelog.setWorkingDir(project.getRootDir());
-				generateChangelog.classpath(project.getConfigurations().getAt(CHANGELOG_GENERATOR_CONFIGURATION_NAME));
-				generateChangelog.doFirst(new Action<Task>() {
-					@Override
-					public void execute(Task task) {
-						generateChangelog.args("--spring.config.location=scripts/release/release-notes-sections.yml", project.property("nextVersion"), outputFile.toString());
-					}
-				});
-			}
-		});
-	}
-
-	private void createChangelogGeneratorConfiguration(Project project) {
-		project.getConfigurations().create(CHANGELOG_GENERATOR_CONFIGURATION_NAME, new Action<Configuration>() {
-			@Override
-			public void execute(Configuration configuration) {
-				configuration.defaultDependencies(new Action<DependencySet>() {
-					@Override
-					public void execute(DependencySet dependencies) {
-						dependencies.add(project.getDependencies().create("spring-io:github-changelog-generator:0.0.6"));
-					}
-				});
-			}
-		});
-	}
-
-	private void createRepository(Project project) {
-		IvyArtifactRepository repository = project.getRepositories().ivy(new Action<IvyArtifactRepository>() {
-			@Override
-			public void execute(IvyArtifactRepository repository) {
-				repository.setUrl("https://github.com/");
-				repository.patternLayout(new Action<IvyPatternRepositoryLayout>() {
-					@Override
-					public void execute(IvyPatternRepositoryLayout layout) {
-						layout.artifact("[organization]/[artifact]/releases/download/v[revision]/[artifact].[ext]");
-					}
-				});
-				repository.getMetadataSources().artifact();
-			}
-		});
-		project.getRepositories().exclusiveContent(new Action<ExclusiveContentRepository>() {
-			@Override
-			public void execute(ExclusiveContentRepository exclusiveContentRepository) {
-				exclusiveContentRepository.forRepositories(repository);
-				exclusiveContentRepository.filter(new Action<InclusiveRepositoryContentDescriptor>() {
-					@Override
-					public void execute(InclusiveRepositoryContentDescriptor descriptor) {
-						descriptor.includeGroup("spring-io");
-					}
-				});
-			}
-		});
-	}
-}

+ 0 - 271
buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneApi.java

@@ -1,271 +0,0 @@
-/*
- * Copyright 2019-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.gradle.github.milestones;
-
-import java.io.IOException;
-import java.time.Instant;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-import java.time.ZoneOffset;
-import java.util.List;
-import java.util.Optional;
-import java.util.TimeZone;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import com.google.common.reflect.TypeToken;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import okhttp3.Interceptor;
-import okhttp3.MediaType;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.RequestBody;
-import okhttp3.Response;
-
-import org.springframework.gradle.github.RepositoryRef;
-
-public class GitHubMilestoneApi {
-	private String baseUrl = "https://api.github.com";
-
-	private OkHttpClient client;
-
-	private final Gson gson = new GsonBuilder()
-			.registerTypeAdapter(LocalDate.class, new LocalDateAdapter().nullSafe())
-			.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter().nullSafe())
-			.create();
-
-	public GitHubMilestoneApi() {
-		this.client = new OkHttpClient.Builder().build();
-	}
-
-	public GitHubMilestoneApi(String gitHubToken) {
-		this.client = new OkHttpClient.Builder()
-				.addInterceptor(new AuthorizationInterceptor(gitHubToken))
-				.build();
-	}
-
-	public void setBaseUrl(String baseUrl) {
-		this.baseUrl = baseUrl;
-	}
-
-	public long findMilestoneNumberByTitle(RepositoryRef repositoryRef, String milestoneTitle) {
-		List<Milestone> milestones = this.getMilestones(repositoryRef);
-		for (Milestone milestone : milestones) {
-			if (milestoneTitle.equals(milestone.getTitle())) {
-				return milestone.getNumber();
-			}
-		}
-		if (milestones.size() <= 100) {
-			throw new RuntimeException("Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef + " Got " + milestones);
-		}
-		throw new RuntimeException("It is possible there are too many open milestones (only 100 are supported). Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef + " Got " + milestones);
-	}
-
-	public List<Milestone> getMilestones(RepositoryRef repositoryRef) {
-		String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName() + "/milestones?per_page=100";
-		Request request = new Request.Builder().get().url(url)
-				.build();
-		try {
-			Response response = this.client.newCall(request).execute();
-			if (!response.isSuccessful()) {
-				throw new RuntimeException("Could not retrieve milestones for repository " + repositoryRef + ". Response " + response);
-			}
-			return this.gson.fromJson(response.body().charStream(), new TypeToken<List<Milestone>>(){}.getType());
-		} catch (IOException e) {
-			throw new RuntimeException("Could not retrieve milestones for repository " + repositoryRef, e);
-		}
-	}
-
-	public boolean isOpenIssuesForMilestoneNumber(RepositoryRef repositoryRef, long milestoneNumber) {
-		String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName() + "/issues?per_page=1&milestone=" + milestoneNumber;
-		Request request = new Request.Builder().get().url(url)
-				.build();
-		try {
-			Response response = this.client.newCall(request).execute();
-			if (!response.isSuccessful()) {
-				throw new RuntimeException("Could not find issues for milestone number " + milestoneNumber + " for repository " + repositoryRef + ". Response " + response);
-			}
-			List<Object> issues = this.gson.fromJson(response.body().charStream(), new TypeToken<List<Object>>(){}.getType());
-			return !issues.isEmpty();
-		} catch (IOException e) {
-			throw new RuntimeException("Could not find issues for milestone number " + milestoneNumber + " for repository " + repositoryRef, e);
-		}
-	}
-
-	/**
-	 * Check if the given milestone is due today or past due.
-	 *
-	 * @param repositoryRef The repository owner/name
-	 * @param milestoneTitle The title of the milestone whose due date should be checked
-	 * @return true if the given milestone is due today or past due, false otherwise
-	 */
-	public boolean isMilestoneDueToday(RepositoryRef repositoryRef, String milestoneTitle) {
-		String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName()
-				+ "/milestones?per_page=100";
-		Request request = new Request.Builder().get().url(url).build();
-		try {
-			Response response = this.client.newCall(request).execute();
-			if (!response.isSuccessful()) {
-				throw new RuntimeException("Could not find milestone with title " + milestoneTitle + " for repository "
-						+ repositoryRef + ". Response " + response);
-			}
-			List<Milestone> milestones = this.gson.fromJson(response.body().charStream(),
-					new TypeToken<List<Milestone>>() {
-					}.getType());
-			for (Milestone milestone : milestones) {
-				if (milestoneTitle.equals(milestone.getTitle())) {
-					LocalDate today = LocalDate.now();
-					return milestone.getDueOn() != null && today.compareTo(milestone.getDueOn().toLocalDate()) >= 0;
-				}
-			}
-			if (milestones.size() <= 100) {
-				throw new RuntimeException("Could not find open milestone with title " + milestoneTitle
-						+ " for repository " + repositoryRef + " Got " + milestones);
-			}
-			throw new RuntimeException(
-					"It is possible there are too many open milestones open (only 100 are supported). Could not find open milestone with title "
-							+ milestoneTitle + " for repository " + repositoryRef + " Got " + milestones);
-		}
-		catch (IOException e) {
-			throw new RuntimeException(
-					"Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef,
-					e);
-		}
-	}
-
-	/**
-	 * Calculate the next release version based on the current version.
-	 *
-	 * The current version must conform to the pattern MAJOR.MINOR.PATCH-SNAPSHOT. If the
-	 * current version is a snapshot of a patch release, then the patch release will be
-	 * returned. For example, if the current version is 5.6.1-SNAPSHOT, then 5.6.1 will be
-	 * returned. If the current version is a snapshot of a version that is not GA (i.e the
-	 * PATCH segment is 0), then GitHub will be queried to find the next milestone or
-	 * release candidate. If no pre-release versions are found, then the next version will
-	 * be assumed to be the GA.
-	 * @param repositoryRef The repository owner/name
-	 * @param currentVersion The current project version
-	 * @return the next matching milestone/release candidate or null if none exist
-	 */
-	public String getNextReleaseMilestone(RepositoryRef repositoryRef, String currentVersion) {
-		Pattern snapshotPattern = Pattern.compile("^([0-9]+)\\.([0-9]+)\\.([0-9]+)-SNAPSHOT$");
-		Matcher snapshotVersion = snapshotPattern.matcher(currentVersion);
-
-		if (snapshotVersion.find()) {
-			String patchSegment = snapshotVersion.group(3);
-			String currentVersionNoIdentifier = currentVersion.replace("-SNAPSHOT", "");
-			if (patchSegment.equals("0")) {
-				String nextPreRelease = getNextPreRelease(repositoryRef, currentVersionNoIdentifier);
-				return nextPreRelease != null ? nextPreRelease : currentVersionNoIdentifier;
-			}
-			else {
-				return currentVersionNoIdentifier;
-			}
-		}
-		else {
-			throw new IllegalStateException(
-					"Cannot calculate next release version because the current project version does not conform to the expected format");
-		}
-	}
-
-	/**
-	 * Calculate the next pre-release version (milestone or release candidate) based on
-	 * the current version.
-	 *
-	 * The current version must conform to the pattern MAJOR.MINOR.PATCH. If no matching
-	 * milestone or release candidate is found in GitHub then it will return null.
-	 * @param repositoryRef The repository owner/name
-	 * @param currentVersionNoIdentifier The current project version without any
-	 * identifier
-	 * @return the next matching milestone/release candidate or null if none exist
-	 */
-	private String getNextPreRelease(RepositoryRef repositoryRef, String currentVersionNoIdentifier) {
-		String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName()
-				+ "/milestones?per_page=100";
-		Request request = new Request.Builder().get().url(url).build();
-		try {
-			Response response = this.client.newCall(request).execute();
-			if (!response.isSuccessful()) {
-				throw new RuntimeException(
-						"Could not get milestones for repository " + repositoryRef + ". Response " + response);
-			}
-			List<Milestone> milestones = this.gson.fromJson(response.body().charStream(),
-					new TypeToken<List<Milestone>>() {
-					}.getType());
-			Optional<String> nextPreRelease = milestones.stream().map(Milestone::getTitle)
-					.filter(m -> m.startsWith(currentVersionNoIdentifier + "-"))
-					.min((m1, m2) -> {
-						Pattern preReleasePattern = Pattern.compile("^.*-([A-Z]+)([0-9]+)$");
-						Matcher matcher1 = preReleasePattern.matcher(m1);
-						Matcher matcher2 = preReleasePattern.matcher(m2);
-						matcher1.find();
-						matcher2.find();
-						if (!matcher1.group(1).equals(matcher2.group(1))) {
-							return m1.compareTo(m2);
-						}
-						else {
-							return Integer.valueOf(matcher1.group(2)).compareTo(Integer.valueOf(matcher2.group(2)));
-						}
-					});
-			return nextPreRelease.orElse(null);
-		}
-		catch (IOException e) {
-			throw new RuntimeException("Could not find open milestones with for repository " + repositoryRef, e);
-		}
-	}
-
-	/**
-	 * Create a milestone.
-	 *
-	 * @param repository The repository owner/name
-	 * @param milestone The milestone containing a title and due date
-	 */
-	public void createMilestone(RepositoryRef repository, Milestone milestone) {
-		String url = this.baseUrl + "/repos/" + repository.getOwner() + "/" + repository.getName() + "/milestones";
-		String json = this.gson.toJson(milestone);
-		RequestBody body = RequestBody.create(MediaType.parse("application/json"), json);
-		Request request = new Request.Builder().url(url).post(body).build();
-		try {
-			Response response = this.client.newCall(request).execute();
-			if (!response.isSuccessful()) {
-				throw new RuntimeException(String.format("Could not create milestone %s for repository %s/%s. Got response %s",
-						milestone.getTitle(), repository.getOwner(), repository.getName(), response));
-			}
-		} catch (IOException ex) {
-			throw new RuntimeException(String.format("Could not create release %s for repository %s/%s",
-					milestone.getTitle(), repository.getOwner(), repository.getName()), ex);
-		}
-	}
-
-	private static class AuthorizationInterceptor implements Interceptor {
-
-		private final String token;
-
-		public AuthorizationInterceptor(String token) {
-			this.token = token;
-		}
-
-		@Override
-		public okhttp3.Response intercept(Chain chain) throws IOException {
-			Request request = chain.request().newBuilder()
-					.addHeader("Authorization", "Bearer " + this.token).build();
-			return chain.proceed(request);
-		}
-	}
-}

+ 0 - 110
buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneHasNoOpenIssuesTask.java

@@ -1,110 +0,0 @@
-/*
- * Copyright 2019-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.gradle.github.milestones;
-
-import org.gradle.api.Action;
-import org.gradle.api.DefaultTask;
-import org.gradle.api.file.RegularFileProperty;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.work.DisableCachingByDefault;
-import org.yaml.snakeyaml.Yaml;
-import org.yaml.snakeyaml.constructor.Constructor;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import org.springframework.gradle.github.RepositoryRef;
-
-@DisableCachingByDefault(because = "the due date needs to be checked every time in case it changes")
-public abstract class GitHubMilestoneHasNoOpenIssuesTask extends DefaultTask {
-	@Input
-	private RepositoryRef repository = new RepositoryRef();
-
-	@Input @Optional
-	private String milestoneTitle;
-
-	@InputFile @Optional
-	public abstract RegularFileProperty getNextVersionFile();
-
-	@Input @Optional
-	private String gitHubAccessToken;
-
-	@OutputFile
-	public abstract RegularFileProperty getIsOpenIssuesFile();
-
-	private GitHubMilestoneApi milestones = new GitHubMilestoneApi();
-
-	@TaskAction
-	public void checkHasNoOpenIssues() throws IOException {
-		if (this.milestoneTitle == null) {
-			File nextVersionFile = getNextVersionFile().getAsFile().get();
-			Yaml yaml = new Yaml(new Constructor(NextVersionYml.class));
-			NextVersionYml nextVersionYml = yaml.load(new FileInputStream(nextVersionFile));
-			String nextVersion = nextVersionYml.getVersion();
-			if (nextVersion == null) {
-				throw new IllegalArgumentException(
-						"Could not find version property in provided file " + nextVersionFile.getName());
-			}
-			this.milestoneTitle = nextVersion;
-		}
-		long milestoneNumber = this.milestones.findMilestoneNumberByTitle(this.repository, this.milestoneTitle);
-		boolean isOpenIssues = this.milestones.isOpenIssuesForMilestoneNumber(this.repository, milestoneNumber);
-		Path isOpenIssuesPath = getIsOpenIssuesFile().getAsFile().get().toPath();
-		Files.writeString(isOpenIssuesPath, String.valueOf(isOpenIssues));
-		if (isOpenIssues) {
-			System.out.println("The repository " + this.repository + " has open issues for milestone with the title " + this.milestoneTitle + " and number " + milestoneNumber);
-		}
-		else {
-			System.out.println("The repository " + this.repository + " has no open issues for milestone with the title " + this.milestoneTitle + " and number " + milestoneNumber);
-		}
-	}
-
-	public RepositoryRef getRepository() {
-		return repository;
-	}
-
-	public void repository(Action<RepositoryRef> repository) {
-		repository.execute(this.repository);
-	}
-
-	public void setRepository(RepositoryRef repository) {
-		this.repository = repository;
-	}
-
-	public String getMilestoneTitle() {
-		return milestoneTitle;
-	}
-
-	public void setMilestoneTitle(String milestoneTitle) {
-		this.milestoneTitle = milestoneTitle;
-	}
-
-	public String getGitHubAccessToken() {
-		return gitHubAccessToken;
-	}
-
-	public void setGitHubAccessToken(String gitHubAccessToken) {
-		this.gitHubAccessToken = gitHubAccessToken;
-		this.milestones = new GitHubMilestoneApi(gitHubAccessToken);
-	}
-}

+ 0 - 93
buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneNextReleaseTask.java

@@ -1,93 +0,0 @@
-/*
- * Copyright 2019-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.gradle.github.milestones;
-
-import org.gradle.api.Action;
-import org.gradle.api.DefaultTask;
-import org.gradle.api.file.RegularFileProperty;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.TaskAction;
-import org.yaml.snakeyaml.DumperOptions;
-import org.yaml.snakeyaml.Yaml;
-import org.yaml.snakeyaml.nodes.Tag;
-import org.yaml.snakeyaml.representer.Representer;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-
-import org.springframework.gradle.github.RepositoryRef;
-
-public abstract class GitHubMilestoneNextReleaseTask extends DefaultTask {
-
-	@Input
-	private RepositoryRef repository = new RepositoryRef();
-
-	@Input
-	@Optional
-	private String gitHubAccessToken;
-
-	private GitHubMilestoneApi milestones = new GitHubMilestoneApi();
-
-	@TaskAction
-	public void calculateNextReleaseMilestone() throws IOException {
-		String currentVersion = getProject().getVersion().toString();
-		String nextPreRelease = this.milestones.getNextReleaseMilestone(this.repository, currentVersion);
-		System.out.println("The next release milestone is: " + nextPreRelease);
-		NextVersionYml nextVersionYml = new NextVersionYml();
-		nextVersionYml.setVersion(nextPreRelease);
-		File outputFile = getNextReleaseFile().get().getAsFile();
-		FileWriter outputWriter = new FileWriter(outputFile);
-		Yaml yaml = getYaml();
-		yaml.dump(nextVersionYml, outputWriter);
-	}
-
-	@OutputFile
-	public abstract RegularFileProperty getNextReleaseFile();
-
-	public RepositoryRef getRepository() {
-		return repository;
-	}
-
-	public void repository(Action<RepositoryRef> repository) {
-		repository.execute(this.repository);
-	}
-
-	public void setRepository(RepositoryRef repository) {
-		this.repository = repository;
-	}
-
-	public String getGitHubAccessToken() {
-		return gitHubAccessToken;
-	}
-
-	public void setGitHubAccessToken(String gitHubAccessToken) {
-		this.gitHubAccessToken = gitHubAccessToken;
-		this.milestones = new GitHubMilestoneApi(gitHubAccessToken);
-	}
-
-	private Yaml getYaml() {
-		Representer representer = new Representer();
-		representer.addClassTag(NextVersionYml.class, Tag.MAP);
-		DumperOptions ymlOptions = new DumperOptions();
-		ymlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
-		return new Yaml(representer, ymlOptions);
-	}
-
-}

+ 0 - 102
buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneNextVersionDueTodayTask.java

@@ -1,102 +0,0 @@
-/*
- * Copyright 2019-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.gradle.github.milestones;
-
-import org.gradle.api.Action;
-import org.gradle.api.DefaultTask;
-import org.gradle.api.file.RegularFileProperty;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.work.DisableCachingByDefault;
-import org.yaml.snakeyaml.Yaml;
-import org.yaml.snakeyaml.constructor.Constructor;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import org.springframework.gradle.github.RepositoryRef;
-
-@DisableCachingByDefault(because = "the due date needs to be checked every time in case it changes")
-public abstract class GitHubMilestoneNextVersionDueTodayTask extends DefaultTask {
-
-	@Input
-	private RepositoryRef repository = new RepositoryRef();
-
-	@Input
-	@Optional
-	private String gitHubAccessToken;
-
-	@InputFile
-	public abstract RegularFileProperty getNextVersionFile();
-
-	@OutputFile
-	public abstract RegularFileProperty getIsDueTodayFile();
-
-	private GitHubMilestoneApi milestones = new GitHubMilestoneApi();
-
-	@TaskAction
-	public void checkReleaseDueToday() throws IOException {
-		File nextVersionFile = getNextVersionFile().getAsFile().get();
-		Yaml yaml = new Yaml(new Constructor(NextVersionYml.class));
-		NextVersionYml nextVersionYml = yaml.load(new FileInputStream(nextVersionFile));
-		String nextVersion = nextVersionYml.getVersion();
-		if (nextVersion == null) {
-			throw new IllegalArgumentException(
-					"Could not find version property in provided file " + nextVersionFile.getName());
-		}
-		boolean milestoneDueToday = this.milestones.isMilestoneDueToday(this.repository, nextVersion);
-		Path isDueTodayPath = getIsDueTodayFile().getAsFile().get().toPath();
-		Files.writeString(isDueTodayPath, String.valueOf(milestoneDueToday));
-		if (milestoneDueToday) {
-			System.out.println("The milestone with the title " + nextVersion + " in the repository " + this.repository
-					+ " is due today");
-		}
-		else {
-			System.out.println("The milestone with the title " + nextVersion + " in the repository "
-					+ this.repository + " is not due yet");
-		}
-
-	}
-
-	public RepositoryRef getRepository() {
-		return repository;
-	}
-
-	public void repository(Action<RepositoryRef> repository) {
-		repository.execute(this.repository);
-	}
-
-	public void setRepository(RepositoryRef repository) {
-		this.repository = repository;
-	}
-
-	public String getGitHubAccessToken() {
-		return gitHubAccessToken;
-	}
-
-	public void setGitHubAccessToken(String gitHubAccessToken) {
-		this.gitHubAccessToken = gitHubAccessToken;
-		this.milestones = new GitHubMilestoneApi(gitHubAccessToken);
-	}
-
-}

+ 0 - 73
buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestonePlugin.java

@@ -1,73 +0,0 @@
-/*
- * Copyright 2019-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.gradle.github.milestones;
-
-import org.gradle.api.Action;
-import org.gradle.api.Plugin;
-import org.gradle.api.Project;
-import org.gradle.api.tasks.TaskProvider;
-
-import org.springframework.gradle.github.RepositoryRef;
-
-public class GitHubMilestonePlugin implements Plugin<Project> {
-	@Override
-	public void apply(Project project) {
-		TaskProvider<GitHubMilestoneNextReleaseTask> nextReleaseMilestoneTask = project.getTasks().register("gitHubNextReleaseMilestone", GitHubMilestoneNextReleaseTask.class, (gitHubMilestoneNextReleaseTask) -> {
-			gitHubMilestoneNextReleaseTask.doNotTrackState("API call to GitHub needs to check for new milestones every time");
-			gitHubMilestoneNextReleaseTask.setGroup("Release");
-			gitHubMilestoneNextReleaseTask.setDescription("Calculates the next release version based on the current version and outputs it to a yaml file");
-			gitHubMilestoneNextReleaseTask.getNextReleaseFile()
-					.fileProvider(project.provider(() -> project.file("next-release.yml")));
-			if (project.hasProperty("gitHubAccessToken")) {
-				gitHubMilestoneNextReleaseTask
-						.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
-			}
-		});
-		project.getTasks().register("gitHubCheckMilestoneHasNoOpenIssues", GitHubMilestoneHasNoOpenIssuesTask.class, (githubCheckMilestoneHasNoOpenIssues) -> {
-			githubCheckMilestoneHasNoOpenIssues.setGroup("Release");
-			githubCheckMilestoneHasNoOpenIssues.setDescription("Checks if there are any open issues for the specified repository and milestone");
-			githubCheckMilestoneHasNoOpenIssues.getIsOpenIssuesFile().value(project.getLayout().getBuildDirectory().file("github/milestones/is-open-issues"));
-			githubCheckMilestoneHasNoOpenIssues.setMilestoneTitle((String) project.findProperty("nextVersion"));
-			if (!project.hasProperty("nextVersion")) {
-				githubCheckMilestoneHasNoOpenIssues.getNextVersionFile().convention(
-						nextReleaseMilestoneTask.flatMap(GitHubMilestoneNextReleaseTask::getNextReleaseFile));
-			}
-			if (project.hasProperty("gitHubAccessToken")) {
-				githubCheckMilestoneHasNoOpenIssues.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
-			}
-		});
-		project.getTasks().register("gitHubCheckNextVersionDueToday", GitHubMilestoneNextVersionDueTodayTask.class, (gitHubMilestoneNextVersionDueTodayTask) -> {
-			gitHubMilestoneNextVersionDueTodayTask.setGroup("Release");
-			gitHubMilestoneNextVersionDueTodayTask.setDescription("Checks if the next release version is due today or past due, will fail if the next version is not due yet");
-			gitHubMilestoneNextVersionDueTodayTask.getIsDueTodayFile().value(project.getLayout().getBuildDirectory().file("github/milestones/is-due-today"));
-			gitHubMilestoneNextVersionDueTodayTask.getNextVersionFile().convention(
-					nextReleaseMilestoneTask.flatMap(GitHubMilestoneNextReleaseTask::getNextReleaseFile));
-			if (project.hasProperty("gitHubAccessToken")) {
-				gitHubMilestoneNextVersionDueTodayTask
-						.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
-			}
-		});
-		project.getTasks().register("scheduleNextRelease", ScheduleNextReleaseTask.class, (scheduleNextRelease) -> {
-			scheduleNextRelease.doNotTrackState("API call to GitHub needs to check for new milestones every time");
-			scheduleNextRelease.setGroup("Release");
-			scheduleNextRelease.setDescription("Schedule the next release (even months only) or release train (series of milestones starting in January or July) based on the current version");
-
-			scheduleNextRelease.setVersion((String) project.findProperty("nextVersion"));
-			scheduleNextRelease.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
-		});
-	}
-}

+ 0 - 23
buildSrc/src/main/java/org/springframework/gradle/github/milestones/LocalDateAdapter.java

@@ -1,23 +0,0 @@
-package org.springframework.gradle.github.milestones;
-
-import java.io.IOException;
-import java.time.LocalDate;
-
-import com.google.gson.TypeAdapter;
-import com.google.gson.stream.JsonReader;
-import com.google.gson.stream.JsonWriter;
-
-/**
- * @author Steve Riesenberg
- */
-class LocalDateAdapter extends TypeAdapter<LocalDate> {
-	@Override
-	public void write(JsonWriter jsonWriter, LocalDate localDate) throws IOException {
-		jsonWriter.value(localDate.toString());
-	}
-
-	@Override
-	public LocalDate read(JsonReader jsonReader) throws IOException {
-		return LocalDate.parse(jsonReader.nextString());
-	}
-}

+ 0 - 25
buildSrc/src/main/java/org/springframework/gradle/github/milestones/LocalDateTimeAdapter.java

@@ -1,25 +0,0 @@
-package org.springframework.gradle.github.milestones;
-
-import java.io.IOException;
-import java.time.LocalDateTime;
-import java.time.ZoneOffset;
-import java.time.format.DateTimeFormatter;
-
-import com.google.gson.TypeAdapter;
-import com.google.gson.stream.JsonReader;
-import com.google.gson.stream.JsonWriter;
-
-/**
- * @author Steve Riesenberg
- */
-class LocalDateTimeAdapter extends TypeAdapter<LocalDateTime> {
-	@Override
-	public void write(JsonWriter jsonWriter, LocalDateTime localDateTime) throws IOException {
-		jsonWriter.value(localDateTime.atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
-	}
-
-	@Override
-	public LocalDateTime read(JsonReader jsonReader) throws IOException {
-		return LocalDateTime.parse(jsonReader.nextString(), DateTimeFormatter.ISO_ZONED_DATE_TIME);
-	}
-}

+ 0 - 67
buildSrc/src/main/java/org/springframework/gradle/github/milestones/Milestone.java

@@ -1,67 +0,0 @@
-/*
- * Copyright 2019-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.gradle.github.milestones;
-
-import com.google.gson.annotations.SerializedName;
-
-import java.time.LocalDateTime;
-import java.util.Date;
-
-/**
- * @author Steve Riesenberg
- */
-public class Milestone {
-	private String title;
-
-	private Long number;
-
-	@SerializedName("due_on")
-	private LocalDateTime dueOn;
-
-	public String getTitle() {
-		return title;
-	}
-
-	public void setTitle(String title) {
-		this.title = title;
-	}
-
-	public Long getNumber() {
-		return number;
-	}
-
-	public void setNumber(Long number) {
-		this.number = number;
-	}
-
-	public LocalDateTime getDueOn() {
-		return dueOn;
-	}
-
-	public void setDueOn(LocalDateTime dueOn) {
-		this.dueOn = dueOn;
-	}
-
-	@Override
-	public String toString() {
-		return "Milestone{" +
-				"title='" + title + '\'' +
-				", number='" + number + '\'' +
-				", dueOn='" + dueOn + '\'' +
-				'}';
-	}
-}

+ 0 - 29
buildSrc/src/main/java/org/springframework/gradle/github/milestones/NextVersionYml.java

@@ -1,29 +0,0 @@
-/*
- * Copyright 2019-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.gradle.github.milestones;
-
-public class NextVersionYml {
-	private String version;
-
-	public String getVersion() {
-		return version;
-	}
-
-	public void setVersion(String version) {
-		this.version = version;
-	}
-}

+ 0 - 147
buildSrc/src/main/java/org/springframework/gradle/github/milestones/ScheduleNextReleaseTask.java

@@ -1,147 +0,0 @@
-/*
- * Copyright 2002-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.gradle.github.milestones;
-
-import java.time.LocalDate;
-import java.time.LocalTime;
-
-import org.gradle.api.Action;
-import org.gradle.api.DefaultTask;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.TaskAction;
-
-import org.springframework.gradle.github.RepositoryRef;
-
-/**
- * @author Steve Riesenberg
- */
-public class ScheduleNextReleaseTask extends DefaultTask {
-	@Input
-	private RepositoryRef repository = new RepositoryRef();
-
-	@Input
-	private String gitHubAccessToken;
-
-	@Input
-	private String version;
-
-	@Input
-	private Integer weekOfMonth;
-
-	@Input
-	private Integer dayOfWeek;
-
-	@TaskAction
-	public void scheduleNextRelease() {
-		GitHubMilestoneApi gitHubMilestoneApi = new GitHubMilestoneApi(this.gitHubAccessToken);
-		String nextReleaseMilestone = gitHubMilestoneApi.getNextReleaseMilestone(this.repository, this.version);
-
-		// If the next release contains a dash (e.g. 5.6.0-RC1), it is already scheduled
-		if (nextReleaseMilestone.contains("-")) {
-			return;
-		}
-
-		// Check to see if a scheduled GA version already exists
-		boolean hasExistingMilestone = gitHubMilestoneApi.getMilestones(this.repository).stream()
-				.anyMatch(milestone -> nextReleaseMilestone.equals(milestone.getTitle()));
-		if (hasExistingMilestone) {
-			return;
-		}
-
-		// Next milestone is either a patch version or minor version
-		// Note: Major versions will be handled like minor and get a release
-		// train which can be manually updated to match the desired schedule.
-		if (nextReleaseMilestone.endsWith(".0")) {
-			// Create M1, M2, M3, RC1 and GA milestones for release train
-			getReleaseTrain(nextReleaseMilestone).getTrainDates().forEach((milestoneTitle, dueOn) -> {
-				Milestone milestone = new Milestone();
-				milestone.setTitle(milestoneTitle);
-				// Note: GitHub seems to store full date/time as UTC then displays
-				// as a date (no time) in your timezone, which means the date will
-				// not always be the same date as we intend.
-				// Using 12pm/noon UTC allows GitHub to schedule and display the
-				// correct date.
-				milestone.setDueOn(dueOn.atTime(LocalTime.NOON));
-				gitHubMilestoneApi.createMilestone(this.repository, milestone);
-			});
-		} else {
-			// Create GA milestone for patch release on the next even month
-			LocalDate startDate = LocalDate.now();
-			LocalDate dueOn = getReleaseTrain(nextReleaseMilestone).getNextReleaseDate(startDate);
-			Milestone milestone = new Milestone();
-			milestone.setTitle(nextReleaseMilestone);
-			milestone.setDueOn(dueOn.atTime(LocalTime.NOON));
-			gitHubMilestoneApi.createMilestone(this.repository, milestone);
-		}
-	}
-
-	private SpringReleaseTrain getReleaseTrain(String nextReleaseMilestone) {
-		SpringReleaseTrainSpec releaseTrainSpec =
-				SpringReleaseTrainSpec.builder()
-						.nextTrain()
-						.version(nextReleaseMilestone)
-						.weekOfMonth(this.weekOfMonth)
-						.dayOfWeek(this.dayOfWeek)
-						.build();
-
-		return new SpringReleaseTrain(releaseTrainSpec);
-	}
-
-	public RepositoryRef getRepository() {
-		return this.repository;
-	}
-
-	public void repository(Action<RepositoryRef> repository) {
-		repository.execute(this.repository);
-	}
-
-	public void setRepository(RepositoryRef repository) {
-		this.repository = repository;
-	}
-
-	public String getGitHubAccessToken() {
-		return this.gitHubAccessToken;
-	}
-
-	public void setGitHubAccessToken(String gitHubAccessToken) {
-		this.gitHubAccessToken = gitHubAccessToken;
-	}
-
-	public String getVersion() {
-		return this.version;
-	}
-
-	public void setVersion(String version) {
-		this.version = version;
-	}
-
-	public Integer getWeekOfMonth() {
-		return weekOfMonth;
-	}
-
-	public void setWeekOfMonth(Integer weekOfMonth) {
-		this.weekOfMonth = weekOfMonth;
-	}
-
-	public Integer getDayOfWeek() {
-		return dayOfWeek;
-	}
-
-	public void setDayOfWeek(Integer dayOfWeek) {
-		this.dayOfWeek = dayOfWeek;
-	}
-}

+ 0 - 136
buildSrc/src/main/java/org/springframework/gradle/github/milestones/SpringReleaseTrain.java

@@ -1,136 +0,0 @@
-/*
- * Copyright 2002-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.gradle.github.milestones;
-
-import java.time.DayOfWeek;
-import java.time.LocalDate;
-import java.time.Month;
-import java.time.Year;
-import java.time.temporal.TemporalAdjuster;
-import java.time.temporal.TemporalAdjusters;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-/**
- * Spring release train generator based on rules contained in a specification.
- * <p>
- * The rules are:
- * <ol>
- *     <li>Train 1 (January-May) or 2 (July-November)</li>
- *     <li>Version number (e.g. 0.1.2, 1.0.0, etc.)</li>
- *     <li>Week of month (1st, 2nd, 3rd, 4th)</li>
- *     <li>Day of week (Monday-Friday)</li>
- *     <li>Year (e.g. 2020, 2021, etc.)</li>
- * </ol>
- *
- * The release train generated will contain M1, M2, M3, RC1 and GA versions
- * mapped to their respective dates in the train.
- *
- * @author Steve Riesenberg
- */
-public final class SpringReleaseTrain {
-	private final SpringReleaseTrainSpec releaseTrainSpec;
-
-	public SpringReleaseTrain(SpringReleaseTrainSpec releaseTrainSpec) {
-		this.releaseTrainSpec = releaseTrainSpec;
-	}
-
-	/**
-	 * Calculate release train dates based on the release train specification.
-	 *
-	 * @return A mapping of release milestones to scheduled release dates
-	 */
-	public Map<String, LocalDate> getTrainDates() {
-		Map<String, LocalDate> releaseDates = new LinkedHashMap<>();
-		switch (this.releaseTrainSpec.getTrain()) {
-			case ONE:
-				addTrainDate(releaseDates, "M1", Month.JANUARY);
-				addTrainDate(releaseDates, "M2", Month.FEBRUARY);
-				addTrainDate(releaseDates, "M3", Month.MARCH);
-				addTrainDate(releaseDates, "RC1", Month.APRIL);
-				addTrainDate(releaseDates, null, Month.MAY);
-				break;
-			case TWO:
-				addTrainDate(releaseDates, "M1", Month.JULY);
-				addTrainDate(releaseDates, "M2", Month.AUGUST);
-				addTrainDate(releaseDates, "M3", Month.SEPTEMBER);
-				addTrainDate(releaseDates, "RC1", Month.OCTOBER);
-				addTrainDate(releaseDates, null, Month.NOVEMBER);
-				break;
-		}
-
-		return releaseDates;
-	}
-
-	/**
-	 * Determine if a given date matches the due date of given version.
-	 *
-	 * @param version The version number (e.g. 5.6.0-M1, 5.6.0, etc.)
-	 * @param expectedDate The expected date
-	 * @return true if the given date matches the due date of the given version, false otherwise
-	 */
-	public boolean isTrainDate(String version, LocalDate expectedDate) {
-		return expectedDate.isEqual(getTrainDates().get(version));
-	}
-
-	/**
-	 * Calculate the next release date following the given date.
-	 * <p>
-	 * The next release date is always on an even month so that a patch release
-	 * is the month after the GA version of a release train. This method does
-	 * not consider the year of the release train, only the given start date.
-	 *
-	 * @param startDate The start date
-	 * @return The next release date following the given date
-	 */
-	public LocalDate getNextReleaseDate(LocalDate startDate) {
-		LocalDate trainDate;
-		LocalDate currentDate = startDate;
-		do {
-			trainDate = calculateReleaseDate(
-					Year.of(currentDate.getYear()),
-					currentDate.getMonth(),
-					this.releaseTrainSpec.getDayOfWeek().getDayOfWeek(),
-					this.releaseTrainSpec.getWeekOfMonth().getDayOffset()
-			);
-			currentDate = currentDate.plusMonths(1);
-		} while (!trainDate.isAfter(startDate) || trainDate.getMonthValue() % 2 != 0);
-
-		return trainDate;
-	}
-
-	private void addTrainDate(Map<String, LocalDate> releaseDates, String milestone, Month month) {
-		LocalDate releaseDate = calculateReleaseDate(
-				this.releaseTrainSpec.getYear(),
-				month,
-				this.releaseTrainSpec.getDayOfWeek().getDayOfWeek(),
-				this.releaseTrainSpec.getWeekOfMonth().getDayOffset()
-		);
-		String suffix = (milestone == null) ? "" : "-" + milestone;
-		releaseDates.put(this.releaseTrainSpec.getVersion() + suffix, releaseDate);
-	}
-
-	private static LocalDate calculateReleaseDate(Year year, Month month, DayOfWeek dayOfWeek, int dayOffset) {
-		TemporalAdjuster nextMonday = TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY);
-		TemporalAdjuster nextDayOfWeek = TemporalAdjusters.nextOrSame(dayOfWeek);
-
-		LocalDate firstDayOfMonth = year.atMonth(month).atDay(1);
-		LocalDate firstMondayOfMonth = firstDayOfMonth.with(nextMonday);
-
-		return firstMondayOfMonth.with(nextDayOfWeek).plusDays(dayOffset);
-	}
-}

+ 0 - 205
buildSrc/src/main/java/org/springframework/gradle/github/milestones/SpringReleaseTrainSpec.java

@@ -1,205 +0,0 @@
-/*
- * Copyright 2002-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.gradle.github.milestones;
-
-import java.time.LocalDate;
-import java.time.Month;
-import java.time.Year;
-
-import org.springframework.util.Assert;
-
-/**
- * A specification for a release train.
- *
- * @author Steve Riesenberg
- * @see SpringReleaseTrain
- */
-public final class SpringReleaseTrainSpec {
-	private final Train train;
-	private final String version;
-	private final WeekOfMonth weekOfMonth;
-	private final DayOfWeek dayOfWeek;
-	private final Year year;
-
-	public SpringReleaseTrainSpec(Train train, String version, WeekOfMonth weekOfMonth, DayOfWeek dayOfWeek, Year year) {
-		this.train = train;
-		this.version = version;
-		this.weekOfMonth = weekOfMonth;
-		this.dayOfWeek = dayOfWeek;
-		this.year = year;
-	}
-
-	public Train getTrain() {
-		return train;
-	}
-
-	public String getVersion() {
-		return version;
-	}
-
-	public WeekOfMonth getWeekOfMonth() {
-		return weekOfMonth;
-	}
-
-	public DayOfWeek getDayOfWeek() {
-		return dayOfWeek;
-	}
-
-	public Year getYear() {
-		return year;
-	}
-
-	public static Builder builder() {
-		return new Builder();
-	}
-
-	public enum WeekOfMonth {
-		FIRST(0), SECOND(7), THIRD(14), FOURTH(21);
-
-		private final int dayOffset;
-
-		WeekOfMonth(int dayOffset) {
-			this.dayOffset = dayOffset;
-		}
-
-		public int getDayOffset() {
-			return dayOffset;
-		}
-	}
-
-	public enum DayOfWeek {
-		MONDAY(java.time.DayOfWeek.MONDAY),
-		TUESDAY(java.time.DayOfWeek.TUESDAY),
-		WEDNESDAY(java.time.DayOfWeek.WEDNESDAY),
-		THURSDAY(java.time.DayOfWeek.THURSDAY),
-		FRIDAY(java.time.DayOfWeek.FRIDAY);
-
-		private final java.time.DayOfWeek dayOfWeek;
-
-		DayOfWeek(java.time.DayOfWeek dayOfWeek) {
-			this.dayOfWeek = dayOfWeek;
-		}
-
-		public java.time.DayOfWeek getDayOfWeek() {
-			return dayOfWeek;
-		}
-	}
-
-	public enum Train {
-		ONE, TWO
-	}
-
-	public static class Builder {
-		private Train train;
-		private String version;
-		private WeekOfMonth weekOfMonth;
-		private DayOfWeek dayOfWeek;
-		private Year year;
-
-		private Builder() {
-		}
-
-		public Builder train(int train) {
-			switch (train) {
-				case 1: this.train = Train.ONE; break;
-				case 2: this.train = Train.TWO; break;
-				default: throw new IllegalArgumentException("Invalid train: " + train);
-			}
-			return this;
-		}
-
-		public Builder train(Train train) {
-			this.train = train;
-			return this;
-		}
-
-		public Builder nextTrain() {
-			// Search for next train starting with this month
-			return nextTrain(LocalDate.now().withDayOfMonth(1));
-		}
-
-		public Builder nextTrain(LocalDate startDate) {
-			Train nextTrain = null;
-
-			// Search for next train from a given start date
-			LocalDate currentDate = startDate;
-			while (nextTrain == null) {
-				if (currentDate.getMonth() == Month.JANUARY) {
-					nextTrain = Train.ONE;
-				} else if (currentDate.getMonth() == Month.JULY) {
-					nextTrain = Train.TWO;
-				}
-
-				currentDate = currentDate.plusMonths(1);
-			}
-
-			return train(nextTrain).year(currentDate.getYear());
-		}
-
-		public Builder version(String version) {
-			this.version = version;
-			return this;
-		}
-
-		public Builder weekOfMonth(int weekOfMonth) {
-			switch (weekOfMonth) {
-				case 1: this.weekOfMonth = WeekOfMonth.FIRST; break;
-				case 2: this.weekOfMonth = WeekOfMonth.SECOND; break;
-				case 3: this.weekOfMonth = WeekOfMonth.THIRD; break;
-				case 4: this.weekOfMonth = WeekOfMonth.FOURTH; break;
-				default: throw new IllegalArgumentException("Invalid weekOfMonth: " + weekOfMonth);
-			}
-			return this;
-		}
-
-		public Builder weekOfMonth(WeekOfMonth weekOfMonth) {
-			this.weekOfMonth = weekOfMonth;
-			return this;
-		}
-
-		public Builder dayOfWeek(int dayOfWeek) {
-			switch (dayOfWeek) {
-				case 1: this.dayOfWeek = DayOfWeek.MONDAY; break;
-				case 2: this.dayOfWeek = DayOfWeek.TUESDAY; break;
-				case 3: this.dayOfWeek = DayOfWeek.WEDNESDAY; break;
-				case 4: this.dayOfWeek = DayOfWeek.THURSDAY; break;
-				case 5: this.dayOfWeek = DayOfWeek.FRIDAY; break;
-				default: throw new IllegalArgumentException("Invalid dayOfWeek: " + dayOfWeek);
-			}
-			return this;
-		}
-
-		public Builder dayOfWeek(DayOfWeek dayOfWeek) {
-			this.dayOfWeek = dayOfWeek;
-			return this;
-		}
-
-		public Builder year(int year) {
-			this.year = Year.of(year);
-			return this;
-		}
-
-		public SpringReleaseTrainSpec build() {
-			Assert.notNull(train, "train cannot be null");
-			Assert.notNull(version, "version cannot be null");
-			Assert.notNull(weekOfMonth, "weekOfMonth cannot be null");
-			Assert.notNull(dayOfWeek, "dayOfWeek cannot be null");
-			Assert.notNull(year, "year cannot be null");
-			return new SpringReleaseTrainSpec(train, version, weekOfMonth, dayOfWeek, year);
-		}
-	}
-}

+ 0 - 130
buildSrc/src/main/java/org/springframework/gradle/github/release/CreateGitHubReleaseTask.java

@@ -1,130 +0,0 @@
-/*
- * Copyright 2002-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.gradle.github.release;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-
-import org.gradle.api.Action;
-import org.gradle.api.DefaultTask;
-import org.gradle.api.Project;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.TaskAction;
-
-import org.springframework.gradle.github.RepositoryRef;
-import org.springframework.gradle.github.changelog.GitHubChangelogPlugin;
-
-/**
- * @author Steve Riesenberg
- */
-public class CreateGitHubReleaseTask extends DefaultTask {
-	@Input
-	private RepositoryRef repository = new RepositoryRef();
-
-	@Input @Optional
-	private String gitHubAccessToken;
-
-	@Input
-	private String version;
-
-	@Input @Optional
-	private String branch = "main";
-
-	@Input
-	private boolean createRelease = false;
-
-	@TaskAction
-	public void createGitHubRelease() {
-		String body = readReleaseNotes();
-		Release release = Release.tag(this.version)
-				.commit(this.branch)
-				.name(this.version)
-				.body(body)
-				.preRelease(this.version.contains("-"))
-				.build();
-
-		System.out.printf("%sCreating GitHub release for %s/%s@%s\n",
-				this.createRelease ? "" : "[DRY RUN] ",
-				this.repository.getOwner(),
-				this.repository.getName(),
-				this.version
-		);
-		System.out.printf("  Release Notes:\n\n----\n%s\n----\n\n", body.trim());
-
-		if (this.createRelease) {
-			GitHubReleaseApi github = new GitHubReleaseApi(this.gitHubAccessToken);
-			github.publishRelease(this.repository, release);
-		}
-	}
-
-	private String readReleaseNotes() {
-		Project project = getProject();
-		File inputFile = project.file(Paths.get(project.getBuildDir().getPath(), GitHubChangelogPlugin.RELEASE_NOTES_PATH));
-		try {
-			return Files.readString(inputFile.toPath());
-		} catch (IOException ex) {
-			throw new RuntimeException("Unable to read release notes from " + inputFile, ex);
-		}
-	}
-
-	public RepositoryRef getRepository() {
-		return repository;
-	}
-
-	public void repository(Action<RepositoryRef> repository) {
-		repository.execute(this.repository);
-	}
-
-	public void setRepository(RepositoryRef repository) {
-		this.repository = repository;
-	}
-
-	public String getGitHubAccessToken() {
-		return gitHubAccessToken;
-	}
-
-	public void setGitHubAccessToken(String gitHubAccessToken) {
-		this.gitHubAccessToken = gitHubAccessToken;
-	}
-
-	public String getVersion() {
-		return version;
-	}
-
-	public void setVersion(String version) {
-		this.version = version;
-	}
-
-	public String getBranch() {
-		return branch;
-	}
-
-	public void setBranch(String branch) {
-		this.branch = branch;
-	}
-
-	public boolean isCreateRelease() {
-		return createRelease;
-	}
-
-	public void setCreateRelease(boolean createRelease) {
-		this.createRelease = createRelease;
-	}
-}

+ 0 - 84
buildSrc/src/main/java/org/springframework/gradle/github/release/DispatchGitHubWorkflowTask.java

@@ -1,84 +0,0 @@
-/*
- * Copyright 2002-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.gradle.github.release;
-
-import org.gradle.api.Action;
-import org.gradle.api.DefaultTask;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.TaskAction;
-
-import org.springframework.gradle.github.RepositoryRef;
-
-/**
- * @author Steve Riesenberg
- */
-public class DispatchGitHubWorkflowTask extends DefaultTask {
-	@Input
-	private RepositoryRef repository = new RepositoryRef();
-
-	@Input
-	private String gitHubAccessToken;
-
-	@Input
-	private String branch;
-
-	@Input
-	private String workflowId;
-
-	@TaskAction
-	public void dispatchGitHubWorkflow() {
-		GitHubActionsApi gitHubActionsApi = new GitHubActionsApi(this.gitHubAccessToken);
-		WorkflowDispatch workflowDispatch = new WorkflowDispatch(this.branch, null);
-		gitHubActionsApi.dispatchWorkflow(this.repository, this.workflowId, workflowDispatch);
-	}
-
-	public RepositoryRef getRepository() {
-		return repository;
-	}
-
-	public void repository(Action<RepositoryRef> repository) {
-		repository.execute(this.repository);
-	}
-
-	public void setRepository(RepositoryRef repository) {
-		this.repository = repository;
-	}
-
-	public String getGitHubAccessToken() {
-		return gitHubAccessToken;
-	}
-
-	public void setGitHubAccessToken(String gitHubAccessToken) {
-		this.gitHubAccessToken = gitHubAccessToken;
-	}
-
-	public String getBranch() {
-		return branch;
-	}
-
-	public void setBranch(String branch) {
-		this.branch = branch;
-	}
-
-	public String getWorkflowId() {
-		return workflowId;
-	}
-
-	public void setWorkflowId(String workflowId) {
-		this.workflowId = workflowId;
-	}
-}

+ 0 - 98
buildSrc/src/main/java/org/springframework/gradle/github/release/GitHubActionsApi.java

@@ -1,98 +0,0 @@
-/*
- * Copyright 2002-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.gradle.github.release;
-
-import java.io.IOException;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import okhttp3.Interceptor;
-import okhttp3.MediaType;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.RequestBody;
-import okhttp3.Response;
-
-import org.springframework.gradle.github.RepositoryRef;
-
-/**
- * Manage GitHub Actions.
- *
- * @author Steve Riesenberg
- */
-public class GitHubActionsApi {
-	private String baseUrl = "https://api.github.com";
-
-	private final OkHttpClient client;
-
-	private final Gson gson = new GsonBuilder().create();
-
-	public GitHubActionsApi() {
-		this.client = new OkHttpClient.Builder().build();
-	}
-
-	public GitHubActionsApi(String gitHubToken) {
-		this.client = new OkHttpClient.Builder()
-				.addInterceptor(new AuthorizationInterceptor(gitHubToken))
-				.build();
-	}
-
-	public void setBaseUrl(String baseUrl) {
-		this.baseUrl = baseUrl;
-	}
-
-	/**
-	 * Create a workflow dispatch event.
-	 *
-	 * @param repository The repository owner/name
-	 * @param workflowId The ID of the workflow or the name of the workflow file name
-	 * @param workflowDispatch The workflow dispatch containing a ref (branch) and optional inputs
-	 */
-	public void dispatchWorkflow(RepositoryRef repository, String workflowId, WorkflowDispatch workflowDispatch) {
-		String url = this.baseUrl + "/repos/" + repository.getOwner() + "/" + repository.getName() + "/actions/workflows/" + workflowId + "/dispatches";
-		String json = this.gson.toJson(workflowDispatch);
-		RequestBody body = RequestBody.create(MediaType.parse("application/json"), json);
-		Request request = new Request.Builder().url(url).post(body).build();
-		try {
-			Response response = this.client.newCall(request).execute();
-			if (!response.isSuccessful()) {
-				throw new RuntimeException(String.format("Could not create workflow dispatch %s for repository %s/%s. Got response %s",
-						workflowId, repository.getOwner(), repository.getName(), response));
-			}
-		} catch (IOException ex) {
-			throw new RuntimeException(String.format("Could not create workflow dispatch %s for repository %s/%s",
-					workflowId, repository.getOwner(), repository.getName()), ex);
-		}
-	}
-
-	private static class AuthorizationInterceptor implements Interceptor {
-		private final String token;
-
-		public AuthorizationInterceptor(String token) {
-			this.token = token;
-		}
-
-		@Override
-		public Response intercept(Chain chain) throws IOException {
-			Request request = chain.request().newBuilder()
-					.addHeader("Authorization", "Bearer " + this.token)
-					.build();
-
-			return chain.proceed(request);
-		}
-	}
-}

+ 0 - 91
buildSrc/src/main/java/org/springframework/gradle/github/release/GitHubReleaseApi.java

@@ -1,91 +0,0 @@
-/*
- * Copyright 2002-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.gradle.github.release;
-
-import java.io.IOException;
-
-import com.google.gson.Gson;
-import okhttp3.Interceptor;
-import okhttp3.MediaType;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.RequestBody;
-import okhttp3.Response;
-
-import org.springframework.gradle.github.RepositoryRef;
-
-/**
- * Manage GitHub releases.
- *
- * @author Steve Riesenberg
- */
-public class GitHubReleaseApi {
-	private String baseUrl = "https://api.github.com";
-
-	private final OkHttpClient httpClient;
-	private Gson gson = new Gson();
-
-	public GitHubReleaseApi(String gitHubAccessToken) {
-		this.httpClient = new OkHttpClient.Builder()
-				.addInterceptor(new AuthorizationInterceptor(gitHubAccessToken))
-				.build();
-	}
-
-	public void setBaseUrl(String baseUrl) {
-		this.baseUrl = baseUrl;
-	}
-
-	/**
-	 * Publish a release with no binary attachments.
-	 *
-	 * @param repository The repository owner/name
-	 * @param release The contents of the release
-	 */
-	public void publishRelease(RepositoryRef repository, Release release) {
-		String url = this.baseUrl + "/repos/" + repository.getOwner() + "/" + repository.getName() + "/releases";
-		String json = this.gson.toJson(release);
-		RequestBody body = RequestBody.create(MediaType.parse("application/json"), json);
-		Request request = new Request.Builder().url(url).post(body).build();
-		try {
-			Response response = this.httpClient.newCall(request).execute();
-			if (!response.isSuccessful()) {
-				throw new RuntimeException(String.format("Could not create release %s for repository %s/%s. Got response %s",
-						release.getName(), repository.getOwner(), repository.getName(), response));
-			}
-		} catch (IOException ex) {
-			throw new RuntimeException(String.format("Could not create release %s for repository %s/%s",
-					release.getName(), repository.getOwner(), repository.getName()), ex);
-		}
-	}
-
-	private static class AuthorizationInterceptor implements Interceptor {
-		private final String token;
-
-		public AuthorizationInterceptor(String token) {
-			this.token = token;
-		}
-
-		@Override
-		public Response intercept(Chain chain) throws IOException {
-			Request request = chain.request().newBuilder()
-					.addHeader("Authorization", "Bearer " + this.token)
-					.build();
-
-			return chain.proceed(request);
-		}
-	}
-}

+ 0 - 54
buildSrc/src/main/java/org/springframework/gradle/github/release/GitHubReleasePlugin.java

@@ -1,54 +0,0 @@
-/*
- * Copyright 2002-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.gradle.github.release;
-
-import groovy.lang.MissingPropertyException;
-import org.gradle.api.Plugin;
-import org.gradle.api.Project;
-
-/**
- * @author Steve Riesenberg
- */
-public class GitHubReleasePlugin implements Plugin<Project> {
-	@Override
-	public void apply(Project project) {
-		project.getTasks().register("createGitHubRelease", CreateGitHubReleaseTask.class, (createGitHubRelease) -> {
-			createGitHubRelease.setGroup("Release");
-			createGitHubRelease.setDescription("Create a github release");
-			createGitHubRelease.dependsOn("generateChangelog");
-
-			createGitHubRelease.setCreateRelease("true".equals(project.findProperty("createRelease")));
-			createGitHubRelease.setVersion((String) project.findProperty("nextVersion"));
-			if (project.hasProperty("branch")) {
-				createGitHubRelease.setBranch((String) project.findProperty("branch"));
-			}
-			createGitHubRelease.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
-			if (createGitHubRelease.isCreateRelease() && createGitHubRelease.getGitHubAccessToken() == null) {
-				throw new MissingPropertyException("Please provide an access token with -PgitHubAccessToken=...");
-			}
-		});
-
-		project.getTasks().register("dispatchGitHubWorkflow", DispatchGitHubWorkflowTask.class, (dispatchGitHubWorkflow) -> {
-			dispatchGitHubWorkflow.setGroup("Release");
-			dispatchGitHubWorkflow.setDescription("Create a workflow_dispatch event on a given branch");
-
-			dispatchGitHubWorkflow.setBranch((String) project.findProperty("branch"));
-			dispatchGitHubWorkflow.setWorkflowId((String) project.findProperty("workflowId"));
-			dispatchGitHubWorkflow.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
-		});
-	}
-}

+ 0 - 156
buildSrc/src/main/java/org/springframework/gradle/github/release/Release.java

@@ -1,156 +0,0 @@
-/*
- * Copyright 2002-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.gradle.github.release;
-
-import com.google.gson.annotations.SerializedName;
-
-/**
- * @author Steve Riesenberg
- */
-public class Release {
-	@SerializedName("tag_name")
-	private final String tag;
-
-	@SerializedName("target_commitish")
-	private final String commit;
-
-	@SerializedName("name")
-	private final String name;
-
-	@SerializedName("body")
-	private final String body;
-
-	@SerializedName("draft")
-	private final boolean draft;
-
-	@SerializedName("prerelease")
-	private final boolean preRelease;
-
-	@SerializedName("generate_release_notes")
-	private final boolean generateReleaseNotes;
-
-	private Release(String tag, String commit, String name, String body, boolean draft, boolean preRelease, boolean generateReleaseNotes) {
-		this.tag = tag;
-		this.commit = commit;
-		this.name = name;
-		this.body = body;
-		this.draft = draft;
-		this.preRelease = preRelease;
-		this.generateReleaseNotes = generateReleaseNotes;
-	}
-
-	public String getTag() {
-		return tag;
-	}
-
-	public String getCommit() {
-		return commit;
-	}
-
-	public String getName() {
-		return name;
-	}
-
-	public String getBody() {
-		return body;
-	}
-
-	public boolean isDraft() {
-		return draft;
-	}
-
-	public boolean isPreRelease() {
-		return preRelease;
-	}
-
-	public boolean isGenerateReleaseNotes() {
-		return generateReleaseNotes;
-	}
-
-	@Override
-	public String toString() {
-		return "Release{" +
-				"tag='" + tag + '\'' +
-				", commit='" + commit + '\'' +
-				", name='" + name + '\'' +
-				", body='" + body + '\'' +
-				", draft=" + draft +
-				", preRelease=" + preRelease +
-				", generateReleaseNotes=" + generateReleaseNotes +
-				'}';
-	}
-
-	public static Builder tag(String tag) {
-		return new Builder().tag(tag);
-	}
-
-	public static Builder commit(String commit) {
-		return new Builder().commit(commit);
-	}
-
-	public static final class Builder {
-		private String tag;
-		private String commit;
-		private String name;
-		private String body;
-		private boolean draft;
-		private boolean preRelease;
-		private boolean generateReleaseNotes;
-
-		private Builder() {
-		}
-
-		public Builder tag(String tag) {
-			this.tag = tag;
-			return this;
-		}
-
-		public Builder commit(String commit) {
-			this.commit = commit;
-			return this;
-		}
-
-		public Builder name(String name) {
-			this.name = name;
-			return this;
-		}
-
-		public Builder body(String body) {
-			this.body = body;
-			return this;
-		}
-
-		public Builder draft(boolean draft) {
-			this.draft = draft;
-			return this;
-		}
-
-		public Builder preRelease(boolean preRelease) {
-			this.preRelease = preRelease;
-			return this;
-		}
-
-		public Builder generateReleaseNotes(boolean generateReleaseNotes) {
-			this.generateReleaseNotes = generateReleaseNotes;
-			return this;
-		}
-
-		public Release build() {
-			return new Release(tag, commit, name, body, draft, preRelease, generateReleaseNotes);
-		}
-	}
-}

+ 0 - 51
buildSrc/src/main/java/org/springframework/gradle/github/release/WorkflowDispatch.java

@@ -1,51 +0,0 @@
-/*
- * Copyright 2002-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.gradle.github.release;
-
-import java.util.Map;
-
-/**
- * @author Steve Riesenberg
- */
-public class WorkflowDispatch {
-	private String ref;
-	private Map<String, Object> inputs;
-
-	public WorkflowDispatch() {
-	}
-
-	public WorkflowDispatch(String ref, Map<String, Object> inputs) {
-		this.ref = ref;
-		this.inputs = inputs;
-	}
-
-	public String getRef() {
-		return ref;
-	}
-
-	public void setRef(String ref) {
-		this.ref = ref;
-	}
-
-	public Map<String, Object> getInputs() {
-		return inputs;
-	}
-
-	public void setInputs(Map<String, Object> inputs) {
-		this.inputs = inputs;
-	}
-}

+ 0 - 82
buildSrc/src/main/java/org/springframework/gradle/github/user/GitHubUserApi.java

@@ -1,82 +0,0 @@
-/*
- * Copyright 2020-2023 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.gradle.github.user;
-
-import java.io.IOException;
-
-import com.google.gson.Gson;
-import okhttp3.Interceptor;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.Response;
-
-/**
- * @author Steve Riesenberg
- */
-public class GitHubUserApi {
-	private String baseUrl = "https://api.github.com";
-
-	private final OkHttpClient httpClient;
-	private Gson gson = new Gson();
-
-	public GitHubUserApi(String gitHubAccessToken) {
-		this.httpClient = new OkHttpClient.Builder()
-				.addInterceptor(new AuthorizationInterceptor(gitHubAccessToken))
-				.build();
-	}
-
-	public void setBaseUrl(String baseUrl) {
-		this.baseUrl = baseUrl;
-	}
-
-	/**
-	 * Retrieve a GitHub user by the personal access token.
-	 *
-	 * @return The GitHub user
-	 */
-	public User getUser() {
-		String url = this.baseUrl + "/user";
-		Request request = new Request.Builder().url(url).get().build();
-		try (Response response = this.httpClient.newCall(request).execute()) {
-			if (!response.isSuccessful()) {
-				throw new RuntimeException(
-						String.format("Unable to retrieve GitHub user." +
-								" Please check the personal access token and try again." +
-								" Got response %s", response));
-			}
-			return this.gson.fromJson(response.body().charStream(), User.class);
-		} catch (IOException ex) {
-			throw new RuntimeException("Unable to retrieve GitHub user.", ex);
-		}
-	}
-
-	private static class AuthorizationInterceptor implements Interceptor {
-		private final String token;
-
-		public AuthorizationInterceptor(String token) {
-			this.token = token;
-		}
-
-		@Override
-		public Response intercept(Chain chain) throws IOException {
-			Request request = chain.request().newBuilder()
-					.addHeader("Authorization", "Bearer " + this.token)
-					.build();
-
-			return chain.proceed(request);
-		}
-	}
-}

+ 0 - 53
buildSrc/src/main/java/org/springframework/gradle/github/user/User.java

@@ -1,53 +0,0 @@
-package org.springframework.gradle.github.user;
-
-/**
- * @author Steve Riesenberg
- */
-public class User {
-	private Long id;
-	private String login;
-	private String name;
-	private String url;
-
-	public Long getId() {
-		return this.id;
-	}
-
-	public void setId(Long id) {
-		this.id = id;
-	}
-
-	public String getLogin() {
-		return this.login;
-	}
-
-	public void setLogin(String login) {
-		this.login = login;
-	}
-
-	public String getName() {
-		return this.name;
-	}
-
-	public void setName(String name) {
-		this.name = name;
-	}
-
-	public String getUrl() {
-		return this.url;
-	}
-
-	public void setUrl(String url) {
-		this.url = url;
-	}
-
-	@Override
-	public String toString() {
-		return "User{" +
-				"id=" + id +
-				", login='" + login + '\'' +
-				", name='" + name + '\'' +
-				", url='" + url + '\'' +
-				'}';
-	}
-}

+ 0 - 123
buildSrc/src/main/java/org/springframework/gradle/sagan/Release.java

@@ -1,123 +0,0 @@
-/*
- * Copyright 2019-2020 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.gradle.sagan;
-
-import java.util.regex.Pattern;
-
-/**
- * Domain object for creating a new release version.
- */
-public class Release {
-	private String version;
-
-	private ReleaseStatus status;
-
-	private boolean current;
-
-	private String referenceDocUrl;
-
-	private String apiDocUrl;
-
-	public String getVersion() {
-		return version;
-	}
-
-	public void setVersion(String version) {
-		this.version = version;
-	}
-
-	public ReleaseStatus getStatus() {
-		return status;
-	}
-
-	public void setStatus(ReleaseStatus status) {
-		this.status = status;
-	}
-
-	public boolean isCurrent() {
-		return current;
-	}
-
-	public void setCurrent(boolean current) {
-		this.current = current;
-	}
-
-	public String getReferenceDocUrl() {
-		return referenceDocUrl;
-	}
-
-	public void setReferenceDocUrl(String referenceDocUrl) {
-		this.referenceDocUrl = referenceDocUrl;
-	}
-
-	public String getApiDocUrl() {
-		return apiDocUrl;
-	}
-
-	public void setApiDocUrl(String apiDocUrl) {
-		this.apiDocUrl = apiDocUrl;
-	}
-
-	@Override
-	public String toString() {
-		return "Release{" +
-				"version='" + version + '\'' +
-				", status=" + status +
-				", current=" + current +
-				", referenceDocUrl='" + referenceDocUrl + '\'' +
-				", apiDocUrl='" + apiDocUrl + '\'' +
-				'}';
-	}
-
-	public enum ReleaseStatus {
-		/**
-		 * Unstable version with limited support
-		 */
-		SNAPSHOT,
-		/**
-		 * Pre-Release version meant to be tested by the community
-		 */
-		PRERELEASE,
-		/**
-		 * Release Generally Available on public artifact repositories and enjoying full support from maintainers
-		 */
-		GENERAL_AVAILABILITY;
-
-		private static final Pattern PRERELEASE_PATTERN = Pattern.compile("[A-Za-z0-9\\.\\-]+?(M|RC)\\d+");
-
-		private static final String SNAPSHOT_SUFFIX = "SNAPSHOT";
-
-		/**
-		 * Parse the ReleaseStatus from a String
-		 * @param version a project version
-		 * @return the release status for this version
-		 */
-		public static ReleaseStatus parse(String version) {
-			if (version == null) {
-				throw new IllegalArgumentException("version cannot be null");
-			}
-			if (version.endsWith(SNAPSHOT_SUFFIX)) {
-				return SNAPSHOT;
-			}
-			if (PRERELEASE_PATTERN.matcher(version).matches()) {
-				return PRERELEASE;
-			}
-			return GENERAL_AVAILABILITY;
-		}
-	}
-
-}

+ 0 - 93
buildSrc/src/main/java/org/springframework/gradle/sagan/SaganApi.java

@@ -1,93 +0,0 @@
-/*
- * Copyright 2019-2020 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.gradle.sagan;
-
-import com.google.gson.Gson;
-import okhttp3.*;
-
-import java.io.IOException;
-import java.util.Base64;
-
-/**
- * Implements necessary calls to the Sagan API See https://spring.io/restdocs/index.html
- */
-public class SaganApi {
-	private String baseUrl = "https://api.spring.io";
-
-	private OkHttpClient client;
-	private Gson gson = new Gson();
-
-	public SaganApi(String username, String gitHubToken) {
-		this.client = new OkHttpClient.Builder()
-				.addInterceptor(new BasicInterceptor(username, gitHubToken))
-				.build();
-	}
-
-	public void setBaseUrl(String baseUrl) {
-		this.baseUrl = baseUrl;
-	}
-
-	public void createReleaseForProject(Release release, String projectName) {
-		String url = this.baseUrl + "/projects/" + projectName + "/releases";
-		String releaseJsonString = gson.toJson(release);
-		RequestBody body = RequestBody.create(MediaType.parse("application/json"), releaseJsonString);
-		Request request = new Request.Builder()
-			.url(url)
-			.post(body)
-			.build();
-		try {
-			Response response = this.client.newCall(request).execute();
-			if (!response.isSuccessful()) {
-				throw new RuntimeException("Could not create release " + release + ". Got response " + response);
-			}
-		} catch (IOException fail) {
-			throw new RuntimeException("Could not create release " + release, fail);
-		}
-	}
-
-	public void deleteReleaseForProject(String release, String projectName) {
-		String url = this.baseUrl + "/projects/" + projectName + "/releases/" + release;
-		Request request = new Request.Builder()
-				.url(url)
-				.delete()
-				.build();
-		try {
-			Response response = this.client.newCall(request).execute();
-			if (!response.isSuccessful()) {
-				throw new RuntimeException("Could not delete release " + release + ". Got response " + response);
-			}
-		} catch (IOException fail) {
-			throw new RuntimeException("Could not delete release " + release, fail);
-		}
-	}
-
-	private static class BasicInterceptor implements Interceptor {
-
-		private final String token;
-
-		public BasicInterceptor(String username, String token) {
-			this.token = Base64.getEncoder().encodeToString((username + ":" + token).getBytes());
-		}
-
-		@Override
-		public okhttp3.Response intercept(Chain chain) throws IOException {
-			Request request = chain.request().newBuilder()
-					.addHeader("Authorization", "Basic " + this.token).build();
-			return chain.proceed(request);
-		}
-	}
-}

+ 0 - 109
buildSrc/src/main/java/org/springframework/gradle/sagan/SaganCreateReleaseTask.java

@@ -1,109 +0,0 @@
-/*
- * Copyright 2019-2023 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.gradle.sagan;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.gradle.api.DefaultTask;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.TaskAction;
-
-import org.springframework.gradle.github.user.GitHubUserApi;
-import org.springframework.gradle.github.user.User;
-import org.springframework.util.Assert;
-
-public class SaganCreateReleaseTask extends DefaultTask {
-
-	private static final Pattern VERSION_PATTERN = Pattern.compile("^([0-9]+)\\.([0-9]+)\\.([0-9]+)(-.+)?$");
-
-	@Input
-	private String gitHubAccessToken;
-	@Input
-	private String version;
-	@Input
-	private String apiDocUrl;
-	@Input
-	private String referenceDocUrl;
-	@Input
-	private String projectName;
-
-	@TaskAction
-	public void saganCreateRelease() {
-		GitHubUserApi github = new GitHubUserApi(this.gitHubAccessToken);
-		User user = github.getUser();
-
-		// Antora reference docs URLs for snapshots do not contain -SNAPSHOT
-		String referenceDocUrl = this.referenceDocUrl;
-		if (this.version.endsWith("-SNAPSHOT")) {
-			Matcher versionMatcher = VERSION_PATTERN.matcher(this.version);
-			Assert.isTrue(versionMatcher.matches(), "Version " + this.version + " does not match expected pattern");
-			String majorVersion = versionMatcher.group(1);
-			String minorVersion = versionMatcher.group(2);
-			String majorMinorVersion = String.format("%s.%s-SNAPSHOT", majorVersion, minorVersion);
-			referenceDocUrl = this.referenceDocUrl.replace("{version}", majorMinorVersion);
-		}
-
-		SaganApi sagan = new SaganApi(user.getLogin(), this.gitHubAccessToken);
-		Release release = new Release();
-		release.setVersion(this.version);
-		release.setApiDocUrl(this.apiDocUrl);
-		release.setReferenceDocUrl(referenceDocUrl);
-		sagan.createReleaseForProject(release, this.projectName);
-	}
-
-	public String getGitHubAccessToken() {
-		return gitHubAccessToken;
-	}
-
-	public void setGitHubAccessToken(String gitHubAccessToken) {
-		this.gitHubAccessToken = gitHubAccessToken;
-	}
-
-	public String getVersion() {
-		return version;
-	}
-
-	public void setVersion(String version) {
-		this.version = version;
-	}
-
-	public String getApiDocUrl() {
-		return apiDocUrl;
-	}
-
-	public void setApiDocUrl(String apiDocUrl) {
-		this.apiDocUrl = apiDocUrl;
-	}
-
-	public String getReferenceDocUrl() {
-		return referenceDocUrl;
-	}
-
-	public void setReferenceDocUrl(String referenceDocUrl) {
-		this.referenceDocUrl = referenceDocUrl;
-	}
-
-	public String getProjectName() {
-		return projectName;
-	}
-
-	public void setProjectName(String projectName) {
-		this.projectName = projectName;
-	}
-
-}

+ 0 - 68
buildSrc/src/main/java/org/springframework/gradle/sagan/SaganDeleteReleaseTask.java

@@ -1,68 +0,0 @@
-/*
- * Copyright 2019-2020 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.gradle.sagan;
-
-import org.gradle.api.DefaultTask;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.TaskAction;
-
-import org.springframework.gradle.github.user.GitHubUserApi;
-import org.springframework.gradle.github.user.User;
-
-public class SaganDeleteReleaseTask extends DefaultTask {
-
-	@Input
-	private String gitHubAccessToken;
-	@Input
-	private String version;
-	@Input
-	private String projectName;
-
-	@TaskAction
-	public void saganCreateRelease() {
-		GitHubUserApi github = new GitHubUserApi(this.gitHubAccessToken);
-		User user = github.getUser();
-
-		SaganApi sagan = new SaganApi(user.getLogin(), this.gitHubAccessToken);
-		sagan.deleteReleaseForProject(this.version, this.projectName);
-	}
-
-	public String getGitHubAccessToken() {
-		return gitHubAccessToken;
-	}
-
-	public void setGitHubAccessToken(String gitHubAccessToken) {
-		this.gitHubAccessToken = gitHubAccessToken;
-	}
-
-	public String getVersion() {
-		return version;
-	}
-
-	public void setVersion(String version) {
-		this.version = version;
-	}
-
-	public String getProjectName() {
-		return projectName;
-	}
-
-	public void setProjectName(String projectName) {
-		this.projectName = projectName;
-	}
-
-}

+ 0 - 47
buildSrc/src/main/java/org/springframework/gradle/sagan/SaganPlugin.java

@@ -1,47 +0,0 @@
-/*
- * Copyright 2019-2020 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.gradle.sagan;
-
-import io.spring.gradle.convention.Utils;
-import org.gradle.api.*;
-
-public class SaganPlugin implements Plugin<Project> {
-	@Override
-	public void apply(Project project) {
-		project.getTasks().register("saganCreateRelease", SaganCreateReleaseTask.class, new Action<SaganCreateReleaseTask>() {
-			@Override
-			public void execute(SaganCreateReleaseTask saganCreateVersion) {
-				saganCreateVersion.setGroup("Release");
-				saganCreateVersion.setDescription("Creates a new version for the specified project on spring.io");
-				saganCreateVersion.setVersion((String) project.findProperty("nextVersion"));
-				saganCreateVersion.setProjectName(Utils.getProjectName(project));
-				saganCreateVersion.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
-			}
-		});
-		project.getTasks().register("saganDeleteRelease", SaganDeleteReleaseTask.class, new Action<SaganDeleteReleaseTask>() {
-			@Override
-			public void execute(SaganDeleteReleaseTask saganDeleteVersion) {
-				saganDeleteVersion.setGroup("Release");
-				saganDeleteVersion.setDescription("Delete a version for the specified project on spring.io");
-				saganDeleteVersion.setVersion((String) project.findProperty("previousVersion"));
-				saganDeleteVersion.setProjectName(Utils.getProjectName(project));
-				saganDeleteVersion.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
-			}
-		});
-	}
-
-}

+ 0 - 44
buildSrc/src/main/java/org/springframework/security/convention/versions/UpdateProjectVersionPlugin.java

@@ -1,44 +0,0 @@
-/*
- * Copyright 2019-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.security.convention.versions;
-
-import org.gradle.api.Action;
-import org.gradle.api.Plugin;
-import org.gradle.api.Project;
-
-public class UpdateProjectVersionPlugin implements Plugin<Project> {
-	@Override
-	public void apply(Project project) {
-		project.getTasks().register("updateProjectVersion", UpdateProjectVersionTask.class, new Action<UpdateProjectVersionTask>() {
-			@Override
-			public void execute(UpdateProjectVersionTask updateProjectVersionTask) {
-				updateProjectVersionTask.setGroup("Release");
-				updateProjectVersionTask.setDescription("Updates the project version to the next release in gradle.properties");
-				updateProjectVersionTask.dependsOn("gitHubNextReleaseMilestone");
-				updateProjectVersionTask.getNextVersionFile().fileProvider(project.provider(() -> project.file("next-release.yml")));
-			}
-		});
-		project.getTasks().register("updateToSnapshotVersion", UpdateToSnapshotVersionTask.class, new Action<UpdateToSnapshotVersionTask>() {
-			@Override
-			public void execute(UpdateToSnapshotVersionTask updateToSnapshotVersionTask) {
-				updateToSnapshotVersionTask.setGroup("Release");
-				updateToSnapshotVersionTask.setDescription(
-						"Updates the project version to the next snapshot in gradle.properties");
-			}
-		});
-	}
-}

+ 0 - 63
buildSrc/src/main/java/org/springframework/security/convention/versions/UpdateProjectVersionTask.java

@@ -1,63 +0,0 @@
-/*
- * Copyright 2019-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.security.convention.versions;
-
-import org.gradle.api.DefaultTask;
-import org.gradle.api.Project;
-import org.gradle.api.file.RegularFileProperty;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.InputFile;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.TaskAction;
-import org.yaml.snakeyaml.Yaml;
-import org.yaml.snakeyaml.constructor.Constructor;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-
-import org.springframework.gradle.github.milestones.NextVersionYml;
-
-public abstract class UpdateProjectVersionTask extends DefaultTask {
-
-	@InputFile
-	public abstract RegularFileProperty getNextVersionFile();
-
-	@TaskAction
-	public void checkReleaseDueToday() throws FileNotFoundException {
-		File nextVersionFile = getNextVersionFile().getAsFile().get();
-		Yaml yaml = new Yaml(new Constructor(NextVersionYml.class));
-		NextVersionYml nextVersionYml = yaml.load(new FileInputStream(nextVersionFile));
-		String nextVersion = nextVersionYml.getVersion();
-		if (nextVersion == null) {
-			throw new IllegalArgumentException(
-					"Could not find version property in provided file " + nextVersionFile.getName());
-		}
-		String currentVersion = getProject().getVersion().toString();
-		File gradlePropertiesFile = getProject().getRootProject().file(Project.GRADLE_PROPERTIES);
-		if (!gradlePropertiesFile.exists()) {
-			return;
-		}
-		System.out.println("Updating the project version in " + Project.GRADLE_PROPERTIES + " from " + currentVersion
-				+ " to " + nextVersion);
-		FileUtils.replaceFileText(gradlePropertiesFile, (gradlePropertiesText) -> {
-			gradlePropertiesText = gradlePropertiesText.replace("version=" + currentVersion, "version=" + nextVersion);
-			return gradlePropertiesText;
-		});
-	}
-
-}

+ 0 - 68
buildSrc/src/main/java/org/springframework/security/convention/versions/UpdateToSnapshotVersionTask.java

@@ -1,68 +0,0 @@
-/*
- * Copyright 2019-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.security.convention.versions;
-
-import org.gradle.api.DefaultTask;
-import org.gradle.api.Project;
-import org.gradle.api.tasks.TaskAction;
-
-import java.io.File;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public abstract class UpdateToSnapshotVersionTask extends DefaultTask {
-
-	private static final String RELEASE_VERSION_PATTERN = "^([0-9]+)\\.([0-9]+)\\.([0-9]+)(-M\\d+|-RC\\d+)?$";
-
-	@TaskAction
-	public void updateToSnapshotVersion() {
-		String currentVersion = getProject().getVersion().toString();
-		File gradlePropertiesFile = getProject().getRootProject().file(Project.GRADLE_PROPERTIES);
-		if (!gradlePropertiesFile.exists()) {
-			return;
-		}
-		String nextVersion = calculateNextSnapshotVersion(currentVersion);
-		System.out.println("Updating the project version in " + Project.GRADLE_PROPERTIES + " from " + currentVersion
-				+ " to " + nextVersion);
-		FileUtils.replaceFileText(gradlePropertiesFile, (gradlePropertiesText) -> {
-			gradlePropertiesText = gradlePropertiesText.replace("version=" + currentVersion, "version=" + nextVersion);
-			return gradlePropertiesText;
-		});
-	}
-
-	private String calculateNextSnapshotVersion(String currentVersion) {
-		Pattern releaseVersionPattern = Pattern.compile(RELEASE_VERSION_PATTERN);
-		Matcher releaseVersion = releaseVersionPattern.matcher(currentVersion);
-
-		if (releaseVersion.find()) {
-			String majorSegment = releaseVersion.group(1);
-			String minorSegment = releaseVersion.group(2);
-			String patchSegment = releaseVersion.group(3);
-			String modifier = releaseVersion.group(4);
-			if (modifier == null) {
-				patchSegment = String.valueOf(Integer.parseInt(patchSegment) + 1);
-			}
-			System.out.println("modifier = " + modifier);
-			return String.format("%s.%s.%s-SNAPSHOT", majorSegment, minorSegment, patchSegment);
-		}
-		else {
-			throw new IllegalStateException(
-					"Cannot calculate next snapshot version because the current project version does not conform to the expected format");
-		}
-	}
-
-}

+ 0 - 85
buildSrc/src/test/java/io/spring/gradle/convention/sagan/SaganApiTests.java

@@ -1,85 +0,0 @@
-/*
- * Copyright 2019-2020 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 io.spring.gradle.convention.sagan;
-
-import okhttp3.mockwebserver.MockResponse;
-import okhttp3.mockwebserver.MockWebServer;
-import okhttp3.mockwebserver.RecordedRequest;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.gradle.sagan.Release;
-import org.springframework.gradle.sagan.SaganApi;
-
-import java.nio.charset.Charset;
-import java.util.concurrent.TimeUnit;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-
-public class SaganApiTests {
-	private MockWebServer server;
-
-	private SaganApi sagan;
-
-	private String baseUrl;
-
-	@BeforeEach
-	public void setup() throws Exception {
-		this.server = new MockWebServer();
-		this.server.start();
-		this.sagan = new SaganApi("user", "mock-oauth-token");
-		this.baseUrl = this.server.url("/api").toString();
-		this.sagan.setBaseUrl(this.baseUrl);
-	}
-
-	@AfterEach
-	public void cleanup() throws Exception {
-		this.server.shutdown();
-	}
-
-	@Test
-	public void createWhenValidThenNoException() throws Exception {
-		this.server.enqueue(new MockResponse());
-		Release release = new Release();
-		release.setVersion("5.6.0-SNAPSHOT");
-		release.setApiDocUrl("https://docs.spring.io/spring-security/site/docs/{version}/api/");
-		release.setReferenceDocUrl("https://docs.spring.io/spring-security/reference/{version}/index.html");
-		this.sagan.createReleaseForProject(release, "spring-security");
-		RecordedRequest request = this.server.takeRequest(1, TimeUnit.SECONDS);
-		assertThat(request.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/projects/spring-security/releases");
-		assertThat(request.getMethod()).isEqualToIgnoringCase("post");
-		assertThat(request.getHeaders().get("Authorization")).isEqualTo("Basic dXNlcjptb2NrLW9hdXRoLXRva2Vu");
-		assertThat(request.getBody().readString(Charset.defaultCharset())).isEqualToIgnoringWhitespace("{\n" +
-				"   \"version\":\"5.6.0-SNAPSHOT\",\n" +
-				"   \"current\":false,\n" +
-				"   \"referenceDocUrl\":\"https://docs.spring.io/spring-security/reference/{version}/index.html\",\n" +
-				"   \"apiDocUrl\":\"https://docs.spring.io/spring-security/site/docs/{version}/api/\"\n" +
-				"}");
-	}
-
-	@Test
-	public void deleteWhenValidThenNoException() throws Exception {
-		this.server.enqueue(new MockResponse());
-		this.sagan.deleteReleaseForProject("5.6.0-SNAPSHOT", "spring-security");
-		RecordedRequest request = this.server.takeRequest(1, TimeUnit.SECONDS);
-		assertThat(request.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/projects/spring-security/releases/5.6.0-SNAPSHOT");
-		assertThat(request.getMethod()).isEqualToIgnoringCase("delete");
-		assertThat(request.getHeaders().get("Authorization")).isEqualTo("Basic dXNlcjptb2NrLW9hdXRoLXRva2Vu");
-		assertThat(request.getBody().readString(Charset.defaultCharset())).isEmpty();
-	}
-}

+ 0 - 1226
buildSrc/src/test/java/org/springframework/gradle/github/milestones/GitHubMilestoneApiTests.java

@@ -1,1226 +0,0 @@
-package org.springframework.gradle.github.milestones;
-
-import java.nio.charset.Charset;
-import java.time.Instant;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.LocalTime;
-import java.time.ZoneId;
-import java.util.concurrent.TimeUnit;
-
-import okhttp3.mockwebserver.MockResponse;
-import okhttp3.mockwebserver.MockWebServer;
-import okhttp3.mockwebserver.RecordedRequest;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import org.springframework.gradle.github.RepositoryRef;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-
-
-public class GitHubMilestoneApiTests {
-	private GitHubMilestoneApi github;
-
-	private RepositoryRef repositoryRef = RepositoryRef.owner("spring-projects").repository("spring-security").build();
-
-	private MockWebServer server;
-
-	private String baseUrl;
-
-	@BeforeEach
-	public void setup() throws Exception {
-		this.server = new MockWebServer();
-		this.server.start();
-		this.github = new GitHubMilestoneApi("mock-oauth-token");
-		this.baseUrl = this.server.url("/api").toString();
-		this.github.setBaseUrl(this.baseUrl);
-	}
-
-	@AfterEach
-	public void cleanup() throws Exception {
-		this.server.shutdown();
-	}
-
-	@Test
-	public void findMilestoneNumberByTitleWhenFoundThenSuccess() throws Exception {
-		String responseJson = "[\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
-				"      \"id\":6611880,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
-				"      \"number\":207,\n" +
-				"      \"title\":\"5.6.x\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jgrandja\",\n" +
-				"         \"id\":10884212,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jgrandja\",\n" +
-				"         \"html_url\":\"https://github.com/jgrandja\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":1,\n" +
-				"      \"closed_issues\":0,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
-				"      \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
-				"      \"due_on\":null,\n" +
-				"      \"closed_at\":null\n" +
-				"   },\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
-				"      \"id\":5884208,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
-				"      \"number\":191,\n" +
-				"      \"title\":\"5.5.0-RC1\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jzheaux\",\n" +
-				"         \"id\":3627351,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jzheaux\",\n" +
-				"         \"html_url\":\"https://github.com/jzheaux\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":21,\n" +
-				"      \"closed_issues\":23,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
-				"      \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
-				"      \"due_on\":\"2021-04-12T07:00:00Z\",\n" +
-				"      \"closed_at\":null\n" +
-				"   }\n" +
-				"]";
-		this.server.enqueue(new MockResponse().setBody(responseJson));
-
-		long milestoneNumberByTitle = this.github.findMilestoneNumberByTitle(this.repositoryRef, "5.5.0-RC1");
-
-		RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
-		assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
-		assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
-
-		assertThat(milestoneNumberByTitle).isEqualTo(191);
-	}
-
-	@Test
-	public void findMilestoneNumberByTitleWhenNotFoundThenException() throws Exception {
-		String responseJson = "[\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
-				"      \"id\":6611880,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
-				"      \"number\":207,\n" +
-				"      \"title\":\"5.6.x\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jgrandja\",\n" +
-				"         \"id\":10884212,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jgrandja\",\n" +
-				"         \"html_url\":\"https://github.com/jgrandja\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":1,\n" +
-				"      \"closed_issues\":0,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
-				"      \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
-				"      \"due_on\":null,\n" +
-				"      \"closed_at\":null\n" +
-				"   },\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
-				"      \"id\":5884208,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
-				"      \"number\":191,\n" +
-				"      \"title\":\"5.5.0-RC1\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jzheaux\",\n" +
-				"         \"id\":3627351,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jzheaux\",\n" +
-				"         \"html_url\":\"https://github.com/jzheaux\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":21,\n" +
-				"      \"closed_issues\":23,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
-				"      \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
-				"      \"due_on\":\"2021-04-12T07:00:00Z\",\n" +
-				"      \"closed_at\":null\n" +
-				"   }\n" +
-				"]";
-		this.server.enqueue(new MockResponse().setBody(responseJson));
-
-		assertThatExceptionOfType(RuntimeException.class)
-				.isThrownBy(() -> this.github.findMilestoneNumberByTitle(this.repositoryRef, "missing"));
-	}
-
-	@Test
-	public void isOpenIssuesForMilestoneNumberWhenAllClosedThenFalse() throws Exception {
-		String responseJson = "[]";
-		long milestoneNumber = 202;
-		this.server.enqueue(new MockResponse().setBody(responseJson));
-
-		assertThat(this.github.isOpenIssuesForMilestoneNumber(this.repositoryRef, milestoneNumber)).isFalse();
-
-		RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
-		assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
-		assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/issues?per_page=1&milestone=" + milestoneNumber);
-	}
-
-	@Test
-	public void isOpenIssuesForMilestoneNumberWhenOpenIssuesThenTrue() throws Exception {
-		String responseJson = "[\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562\",\n" +
-				"      \"repository_url\":\"https://api.github.com/repos/spring-projects/spring-security\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/labels{/name}\",\n" +
-				"      \"comments_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/comments\",\n" +
-				"      \"events_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/events\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/pull/9562\",\n" +
-				"      \"id\":851886504,\n" +
-				"      \"node_id\":\"MDExOlB1bGxSZXF1ZXN0NjEwMjMzMDcw\",\n" +
-				"      \"number\":9562,\n" +
-				"      \"title\":\"Add package-list\",\n" +
-				"      \"user\":{\n" +
-				"         \"login\":\"jzheaux\",\n" +
-				"         \"id\":3627351,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jzheaux\",\n" +
-				"         \"html_url\":\"https://github.com/jzheaux\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"labels\":[\n" +
-				"         {\n" +
-				"            \"id\":322225043,\n" +
-				"            \"node_id\":\"MDU6TGFiZWwzMjIyMjUwNDM=\",\n" +
-				"            \"url\":\"https://api.github.com/repos/spring-projects/spring-security/labels/in:%20build\",\n" +
-				"            \"name\":\"in: build\",\n" +
-				"            \"color\":\"e8f9de\",\n" +
-				"            \"default\":false,\n" +
-				"            \"description\":\"An issue in the build\"\n" +
-				"         },\n" +
-				"         {\n" +
-				"            \"id\":322225079,\n" +
-				"            \"node_id\":\"MDU6TGFiZWwzMjIyMjUwNzk=\",\n" +
-				"            \"url\":\"https://api.github.com/repos/spring-projects/spring-security/labels/type:%20bug\",\n" +
-				"            \"name\":\"type: bug\",\n" +
-				"            \"color\":\"e3d9fc\",\n" +
-				"            \"default\":false,\n" +
-				"            \"description\":\"A general bug\"\n" +
-				"         }\n" +
-				"      ],\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"locked\":false,\n" +
-				"      \"assignee\":{\n" +
-				"         \"login\":\"rwinch\",\n" +
-				"         \"id\":362503,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjM2MjUwMw==\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/362503?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/rwinch\",\n" +
-				"         \"html_url\":\"https://github.com/rwinch\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/rwinch/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/rwinch/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/rwinch/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/rwinch/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/rwinch/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/rwinch/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/rwinch/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/rwinch/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/rwinch/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"assignees\":[\n" +
-				"         {\n" +
-				"            \"login\":\"rwinch\",\n" +
-				"            \"id\":362503,\n" +
-				"            \"node_id\":\"MDQ6VXNlcjM2MjUwMw==\",\n" +
-				"            \"avatar_url\":\"https://avatars.githubusercontent.com/u/362503?v=4\",\n" +
-				"            \"gravatar_id\":\"\",\n" +
-				"            \"url\":\"https://api.github.com/users/rwinch\",\n" +
-				"            \"html_url\":\"https://github.com/rwinch\",\n" +
-				"            \"followers_url\":\"https://api.github.com/users/rwinch/followers\",\n" +
-				"            \"following_url\":\"https://api.github.com/users/rwinch/following{/other_user}\",\n" +
-				"            \"gists_url\":\"https://api.github.com/users/rwinch/gists{/gist_id}\",\n" +
-				"            \"starred_url\":\"https://api.github.com/users/rwinch/starred{/owner}{/repo}\",\n" +
-				"            \"subscriptions_url\":\"https://api.github.com/users/rwinch/subscriptions\",\n" +
-				"            \"organizations_url\":\"https://api.github.com/users/rwinch/orgs\",\n" +
-				"            \"repos_url\":\"https://api.github.com/users/rwinch/repos\",\n" +
-				"            \"events_url\":\"https://api.github.com/users/rwinch/events{/privacy}\",\n" +
-				"            \"received_events_url\":\"https://api.github.com/users/rwinch/received_events\",\n" +
-				"            \"type\":\"User\",\n" +
-				"            \"site_admin\":false\n" +
-				"         }\n" +
-				"      ],\n" +
-				"      \"milestone\":{\n" +
-				"         \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
-				"         \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
-				"         \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
-				"         \"id\":5884208,\n" +
-				"         \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
-				"         \"number\":191,\n" +
-				"         \"title\":\"5.5.0-RC1\",\n" +
-				"         \"description\":\"\",\n" +
-				"         \"creator\":{\n" +
-				"            \"login\":\"jzheaux\",\n" +
-				"            \"id\":3627351,\n" +
-				"            \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
-				"            \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
-				"            \"gravatar_id\":\"\",\n" +
-				"            \"url\":\"https://api.github.com/users/jzheaux\",\n" +
-				"            \"html_url\":\"https://github.com/jzheaux\",\n" +
-				"            \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
-				"            \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
-				"            \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
-				"            \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
-				"            \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
-				"            \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
-				"            \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
-				"            \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
-				"            \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
-				"            \"type\":\"User\",\n" +
-				"            \"site_admin\":false\n" +
-				"         },\n" +
-				"         \"open_issues\":21,\n" +
-				"         \"closed_issues\":23,\n" +
-				"         \"state\":\"open\",\n" +
-				"         \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
-				"         \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
-				"         \"due_on\":\"2021-04-12T07:00:00Z\",\n" +
-				"         \"closed_at\":null\n" +
-				"      },\n" +
-				"      \"comments\":0,\n" +
-				"      \"created_at\":\"2021-04-06T23:47:10Z\",\n" +
-				"      \"updated_at\":\"2021-04-07T17:00:00Z\",\n" +
-				"      \"closed_at\":null,\n" +
-				"      \"author_association\":\"MEMBER\",\n" +
-				"      \"active_lock_reason\":null,\n" +
-				"      \"pull_request\":{\n" +
-				"         \"url\":\"https://api.github.com/repos/spring-projects/spring-security/pulls/9562\",\n" +
-				"         \"html_url\":\"https://github.com/spring-projects/spring-security/pull/9562\",\n" +
-				"         \"diff_url\":\"https://github.com/spring-projects/spring-security/pull/9562.diff\",\n" +
-				"         \"patch_url\":\"https://github.com/spring-projects/spring-security/pull/9562.patch\"\n" +
-				"      },\n" +
-				"      \"body\":\"Closes gh-9528\\r\\n\\r\\n<!--\\r\\nFor Security Vulnerabilities, please use https://pivotal.io/security#reporting\\r\\n-->\\r\\n\\r\\n<!--\\r\\nBefore creating new features, we recommend creating an issue to discuss the feature. This ensures that everyone is on the same page before extensive work is done.\\r\\n\\r\\nThanks for contributing to Spring Security. Please provide a brief description of your pull-request and reference any related issue numbers (prefix references with gh-).\\r\\n-->\\r\\n\",\n" +
-				"      \"performed_via_github_app\":null\n" +
-				"   }\n" +
-				"]";
-		long milestoneNumber = 191;
-		this.server.enqueue(new MockResponse().setBody(responseJson));
-
-		assertThat(this.github.isOpenIssuesForMilestoneNumber(this.repositoryRef, milestoneNumber)).isTrue();
-
-		RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
-		assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
-		assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/issues?per_page=1&milestone=" + milestoneNumber);
-	}
-
-	@Test
-	public void isMilestoneDueTodayWhenNotFoundThenException() throws Exception {
-		String responseJson = "[\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
-				"      \"id\":6611880,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
-				"      \"number\":207,\n" +
-				"      \"title\":\"5.6.x\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jgrandja\",\n" +
-				"         \"id\":10884212,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jgrandja\",\n" +
-				"         \"html_url\":\"https://github.com/jgrandja\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":1,\n" +
-				"      \"closed_issues\":0,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
-				"      \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
-				"      \"due_on\":null,\n" +
-				"      \"closed_at\":null\n" +
-				"   },\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
-				"      \"id\":5884208,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
-				"      \"number\":191,\n" +
-				"      \"title\":\"5.5.0-RC1\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jzheaux\",\n" +
-				"         \"id\":3627351,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jzheaux\",\n" +
-				"         \"html_url\":\"https://github.com/jzheaux\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":21,\n" +
-				"      \"closed_issues\":23,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
-				"      \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
-				"      \"due_on\":\"2021-04-12T07:00:00Z\",\n" +
-				"      \"closed_at\":null\n" +
-				"   }\n" +
-				"]";
-		this.server.enqueue(new MockResponse().setBody(responseJson));
-
-		assertThatExceptionOfType(RuntimeException.class)
-				.isThrownBy(() -> this.github.isMilestoneDueToday(this.repositoryRef, "missing"));
-	}
-
-	@Test
-	public void isMilestoneDueTodayWhenPastDueThenTrue() throws Exception {
-		String responseJson = "[\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
-				"      \"id\":6611880,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
-				"      \"number\":207,\n" +
-				"      \"title\":\"5.6.x\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jgrandja\",\n" +
-				"         \"id\":10884212,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jgrandja\",\n" +
-				"         \"html_url\":\"https://github.com/jgrandja\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":1,\n" +
-				"      \"closed_issues\":0,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
-				"      \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
-				"      \"due_on\":null,\n" +
-				"      \"closed_at\":null\n" +
-				"   },\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
-				"      \"id\":5884208,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
-				"      \"number\":191,\n" +
-				"      \"title\":\"5.5.0-RC1\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jzheaux\",\n" +
-				"         \"id\":3627351,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jzheaux\",\n" +
-				"         \"html_url\":\"https://github.com/jzheaux\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":21,\n" +
-				"      \"closed_issues\":23,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
-				"      \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
-				"      \"due_on\":\"2021-04-12T07:00:00Z\",\n" +
-				"      \"closed_at\":null\n" +
-				"   }\n" +
-				"]";
-		this.server.enqueue(new MockResponse().setBody(responseJson));
-
-		boolean dueToday = this.github.isMilestoneDueToday(this.repositoryRef, "5.5.0-RC1");
-
-		RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
-		assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
-		assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
-
-		assertThat(dueToday).isTrue();
-	}
-
-	@Test
-	public void isMilestoneDueTodayWhenDueTodayThenTrue() throws Exception {
-		String responseJson = "[\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
-				"      \"id\":6611880,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
-				"      \"number\":207,\n" +
-				"      \"title\":\"5.6.x\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jgrandja\",\n" +
-				"         \"id\":10884212,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jgrandja\",\n" +
-				"         \"html_url\":\"https://github.com/jgrandja\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":1,\n" +
-				"      \"closed_issues\":0,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
-				"      \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
-				"      \"due_on\":null,\n" +
-				"      \"closed_at\":null\n" +
-				"   },\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
-				"      \"id\":5884208,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
-				"      \"number\":191,\n" +
-				"      \"title\":\"5.5.0-RC1\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jzheaux\",\n" +
-				"         \"id\":3627351,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jzheaux\",\n" +
-				"         \"html_url\":\"https://github.com/jzheaux\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":21,\n" +
-				"      \"closed_issues\":23,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
-				"      \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
-				"      \"due_on\":\"" + LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant().toString() + "\",\n" +
-				"      \"closed_at\":null\n" +
-				"   }\n" +
-				"]";
-		this.server.enqueue(new MockResponse().setBody(responseJson));
-
-		boolean dueToday = this.github.isMilestoneDueToday(this.repositoryRef, "5.5.0-RC1");
-
-		RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
-		assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
-		assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
-
-		assertThat(dueToday).isTrue();
-	}
-
-	@Test
-	public void isMilestoneDueTodayWhenNoDueDateThenFalse() throws Exception {
-		String responseJson = "[\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
-				"      \"id\":6611880,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
-				"      \"number\":207,\n" +
-				"      \"title\":\"5.6.x\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jgrandja\",\n" +
-				"         \"id\":10884212,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jgrandja\",\n" +
-				"         \"html_url\":\"https://github.com/jgrandja\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":1,\n" +
-				"      \"closed_issues\":0,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
-				"      \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
-				"      \"due_on\":null,\n" +
-				"      \"closed_at\":null\n" +
-				"   },\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
-				"      \"id\":5884208,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
-				"      \"number\":191,\n" +
-				"      \"title\":\"5.5.0-RC1\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jzheaux\",\n" +
-				"         \"id\":3627351,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jzheaux\",\n" +
-				"         \"html_url\":\"https://github.com/jzheaux\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":21,\n" +
-				"      \"closed_issues\":23,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
-				"      \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
-				"      \"due_on\":null,\n" +
-				"      \"closed_at\":null\n" +
-				"   }\n" +
-				"]";
-		this.server.enqueue(new MockResponse().setBody(responseJson));
-
-		boolean dueToday = this.github.isMilestoneDueToday(this.repositoryRef, "5.5.0-RC1");
-
-		RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
-		assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
-		assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
-
-		assertThat(dueToday).isFalse();
-	}
-
-	@Test
-	public void isMilestoneDueTodayWhenDueDateInFutureThenFalse() throws Exception {
-		String responseJson = "[\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
-				"      \"id\":6611880,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
-				"      \"number\":207,\n" +
-				"      \"title\":\"5.6.x\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jgrandja\",\n" +
-				"         \"id\":10884212,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jgrandja\",\n" +
-				"         \"html_url\":\"https://github.com/jgrandja\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":1,\n" +
-				"      \"closed_issues\":0,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
-				"      \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
-				"      \"due_on\":null,\n" +
-				"      \"closed_at\":null\n" +
-				"   },\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
-				"      \"id\":5884208,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
-				"      \"number\":191,\n" +
-				"      \"title\":\"5.5.0-RC1\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jzheaux\",\n" +
-				"         \"id\":3627351,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jzheaux\",\n" +
-				"         \"html_url\":\"https://github.com/jzheaux\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":21,\n" +
-				"      \"closed_issues\":23,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
-				"      \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
-				"      \"due_on\":\"3000-04-12T07:00:00Z\",\n" +
-				"      \"closed_at\":null\n" +
-				"   }\n" +
-				"]";
-		this.server.enqueue(new MockResponse().setBody(responseJson));
-
-		boolean dueToday = this.github.isMilestoneDueToday(this.repositoryRef, "5.5.0-RC1");
-
-		RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
-		assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
-		assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
-
-		assertThat(dueToday).isFalse();
-	}
-
-	@Test
-	public void calculateNextReleaseMilestoneWhenCurrentVersionIsNotSnapshotThenException() {
-		assertThatExceptionOfType(RuntimeException.class)
-				.isThrownBy(() -> this.github.getNextReleaseMilestone(this.repositoryRef, "5.5.0-RC1"));
-	}
-
-	@Test
-	public void calculateNextReleaseMilestoneWhenPatchSegmentGreaterThan0ThenReturnsVersionWithoutSnapshot() {
-		String nextVersion = this.github.getNextReleaseMilestone(this.repositoryRef, "5.5.1-SNAPSHOT");
-
-		assertThat(nextVersion).isEqualTo("5.5.1");
-	}
-
-	@Test
-	public void calculateNextReleaseMilestoneWhenMilestoneAndRcExistThenReturnsMilestone() throws Exception {
-		String responseJson = "[\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
-				"      \"id\":6611880,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
-				"      \"number\":207,\n" +
-				"      \"title\":\"5.5.0-M1\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jgrandja\",\n" +
-				"         \"id\":10884212,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jgrandja\",\n" +
-				"         \"html_url\":\"https://github.com/jgrandja\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":1,\n" +
-				"      \"closed_issues\":0,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
-				"      \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
-				"      \"due_on\":null,\n" +
-				"      \"closed_at\":null\n" +
-				"   },\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
-				"      \"id\":5884208,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
-				"      \"number\":191,\n" +
-				"      \"title\":\"5.5.0-RC1\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jzheaux\",\n" +
-				"         \"id\":3627351,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jzheaux\",\n" +
-				"         \"html_url\":\"https://github.com/jzheaux\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":21,\n" +
-				"      \"closed_issues\":23,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
-				"      \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
-				"      \"due_on\":\"3000-04-12T07:00:00Z\",\n" +
-				"      \"closed_at\":null\n" +
-				"   }\n" +
-				"]";
-		this.server.enqueue(new MockResponse().setBody(responseJson));
-
-		String nextVersion = this.github.getNextReleaseMilestone(this.repositoryRef, "5.5.0-SNAPSHOT");
-
-		RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
-		assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
-		assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
-
-		assertThat(nextVersion).isEqualTo("5.5.0-M1");
-	}
-
-	@Test
-	public void calculateNextReleaseMilestoneWhenTwoMilestonesExistThenReturnsSmallerMilestone() throws Exception {
-		String responseJson = "[\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
-				"      \"id\":6611880,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
-				"      \"number\":207,\n" +
-				"      \"title\":\"5.5.0-M9\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jgrandja\",\n" +
-				"         \"id\":10884212,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jgrandja\",\n" +
-				"         \"html_url\":\"https://github.com/jgrandja\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":1,\n" +
-				"      \"closed_issues\":0,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
-				"      \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
-				"      \"due_on\":null,\n" +
-				"      \"closed_at\":null\n" +
-				"   },\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
-				"      \"id\":5884208,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
-				"      \"number\":191,\n" +
-				"      \"title\":\"5.5.0-M10\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jzheaux\",\n" +
-				"         \"id\":3627351,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jzheaux\",\n" +
-				"         \"html_url\":\"https://github.com/jzheaux\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":21,\n" +
-				"      \"closed_issues\":23,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
-				"      \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
-				"      \"due_on\":\"3000-04-12T07:00:00Z\",\n" +
-				"      \"closed_at\":null\n" +
-				"   }\n" +
-				"]";
-		this.server.enqueue(new MockResponse().setBody(responseJson));
-
-		String nextVersion = this.github.getNextReleaseMilestone(this.repositoryRef, "5.5.0-SNAPSHOT");
-
-		RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
-		assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
-		assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
-
-		assertThat(nextVersion).isEqualTo("5.5.0-M9");
-	}
-
-	@Test
-	public void calculateNextReleaseMilestoneWhenTwoRcsExistThenReturnsSmallerRc() throws Exception {
-		String responseJson = "[\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
-				"      \"id\":6611880,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
-				"      \"number\":207,\n" +
-				"      \"title\":\"5.5.0-RC9\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jgrandja\",\n" +
-				"         \"id\":10884212,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jgrandja\",\n" +
-				"         \"html_url\":\"https://github.com/jgrandja\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":1,\n" +
-				"      \"closed_issues\":0,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
-				"      \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
-				"      \"due_on\":null,\n" +
-				"      \"closed_at\":null\n" +
-				"   },\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
-				"      \"id\":5884208,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
-				"      \"number\":191,\n" +
-				"      \"title\":\"5.5.0-RC10\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jzheaux\",\n" +
-				"         \"id\":3627351,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jzheaux\",\n" +
-				"         \"html_url\":\"https://github.com/jzheaux\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":21,\n" +
-				"      \"closed_issues\":23,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
-				"      \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
-				"      \"due_on\":\"3000-04-12T07:00:00Z\",\n" +
-				"      \"closed_at\":null\n" +
-				"   }\n" +
-				"]";
-		this.server.enqueue(new MockResponse().setBody(responseJson));
-
-		String nextVersion = this.github.getNextReleaseMilestone(this.repositoryRef, "5.5.0-SNAPSHOT");
-
-		RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
-		assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
-		assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
-
-		assertThat(nextVersion).isEqualTo("5.5.0-RC9");
-	}
-
-	@Test
-	public void calculateNextReleaseMilestoneWhenNoPreReleaseThenReturnsVersionWithoutSnapshot() throws Exception {
-		String responseJson = "[\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
-				"      \"id\":6611880,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
-				"      \"number\":207,\n" +
-				"      \"title\":\"5.6.x\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jgrandja\",\n" +
-				"         \"id\":10884212,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jgrandja\",\n" +
-				"         \"html_url\":\"https://github.com/jgrandja\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":1,\n" +
-				"      \"closed_issues\":0,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
-				"      \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
-				"      \"due_on\":null,\n" +
-				"      \"closed_at\":null\n" +
-				"   },\n" +
-				"   {\n" +
-				"      \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
-				"      \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
-				"      \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
-				"      \"id\":5884208,\n" +
-				"      \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
-				"      \"number\":191,\n" +
-				"      \"title\":\"5.4.3\",\n" +
-				"      \"description\":\"\",\n" +
-				"      \"creator\":{\n" +
-				"         \"login\":\"jzheaux\",\n" +
-				"         \"id\":3627351,\n" +
-				"         \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
-				"         \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
-				"         \"gravatar_id\":\"\",\n" +
-				"         \"url\":\"https://api.github.com/users/jzheaux\",\n" +
-				"         \"html_url\":\"https://github.com/jzheaux\",\n" +
-				"         \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
-				"         \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
-				"         \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
-				"         \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
-				"         \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
-				"         \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
-				"         \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
-				"         \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
-				"         \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
-				"         \"type\":\"User\",\n" +
-				"         \"site_admin\":false\n" +
-				"      },\n" +
-				"      \"open_issues\":21,\n" +
-				"      \"closed_issues\":23,\n" +
-				"      \"state\":\"open\",\n" +
-				"      \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
-				"      \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
-				"      \"due_on\":\"2021-04-12T07:00:00Z\",\n" +
-				"      \"closed_at\":null\n" +
-				"   }\n" +
-				"]";
-		this.server.enqueue(new MockResponse().setBody(responseJson));
-
-		String nextVersion = this.github.getNextReleaseMilestone(this.repositoryRef, "5.5.0-SNAPSHOT");
-
-		RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
-		assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
-		assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
-
-		assertThat(nextVersion).isEqualTo("5.5.0");
-	}
-
-	@Test
-	public void createMilestoneWhenValidParametersThenSuccess() throws Exception {
-		this.server.enqueue(new MockResponse().setResponseCode(204));
-		Milestone milestone = new Milestone();
-		milestone.setTitle("1.0.0");
-		milestone.setDueOn(LocalDate.of(2022, 5, 4).atTime(LocalTime.NOON));
-		this.github.createMilestone(this.repositoryRef, milestone);
-
-		RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
-		assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("post");
-		assertThat(recordedRequest.getRequestUrl().toString())
-				.isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones");
-		assertThat(recordedRequest.getBody().readString(Charset.defaultCharset()))
-				.isEqualTo("{\"title\":\"1.0.0\",\"due_on\":\"2022-05-04T12:00:00Z\"}");
-	}
-
-	@Test
-	public void createMilestoneWhenErrorResponseThenException() throws Exception {
-		this.server.enqueue(new MockResponse().setResponseCode(400));
-		assertThatExceptionOfType(RuntimeException.class)
-				.isThrownBy(() -> this.github.createMilestone(this.repositoryRef, new Milestone()));
-	}
-
-}

+ 0 - 245
buildSrc/src/test/java/org/springframework/gradle/github/milestones/SpringReleaseTrainTests.java

@@ -1,245 +0,0 @@
-/*
- * Copyright 2002-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.gradle.github.milestones;
-
-import java.time.LocalDate;
-import java.time.Year;
-import java.util.Map;
-
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.CsvSource;
-
-import org.springframework.gradle.github.milestones.SpringReleaseTrainSpec.Train;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * @author Steve Riesenberg
- */
-public class SpringReleaseTrainTests {
-	@ParameterizedTest
-	@CsvSource({
-			"2019-12-31, ONE, 2020",
-			"2020-01-01, ONE, 2020",
-			"2020-01-31, ONE, 2020",
-			"2020-02-01, TWO, 2020",
-			"2020-07-31, TWO, 2020",
-			"2020-08-01, ONE, 2021"
-	})
-	public void nextTrainWhenBoundaryConditionsThenSuccess(LocalDate startDate, Train expectedTrain, Year expectedYear) {
-		SpringReleaseTrainSpec releaseTrainSpec =
-				SpringReleaseTrainSpec.builder()
-						.nextTrain(startDate)
-						.version("1.0.0")
-						.weekOfMonth(2)
-						.dayOfWeek(2)
-						.build();
-		assertThat(releaseTrainSpec.getTrain()).isEqualTo(expectedTrain);
-		assertThat(releaseTrainSpec.getYear()).isEqualTo(expectedYear);
-	}
-
-	@Test
-	public void getTrainDatesWhenTrainOneIsSecondTuesdayOf2020ThenSuccess() {
-		SpringReleaseTrainSpec releaseTrainSpec =
-				SpringReleaseTrainSpec.builder()
-						.train(1)
-						.version("1.0.0")
-						.weekOfMonth(2)
-						.dayOfWeek(2)
-						.year(2020)
-						.build();
-
-		SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
-		Map<String, LocalDate> trainDates = releaseTrain.getTrainDates();
-		assertThat(trainDates).hasSize(5);
-		assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2020, 1, 14));
-		assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2020, 2, 11));
-		assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2020, 3, 10));
-		assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2020, 4, 14));
-		assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2020, 5, 12));
-	}
-
-	@Test
-	public void getTrainDatesWhenTrainTwoIsSecondTuesdayOf2020ThenSuccess() {
-		SpringReleaseTrainSpec releaseTrainSpec =
-				SpringReleaseTrainSpec.builder()
-						.train(2)
-						.version("1.0.0")
-						.weekOfMonth(2)
-						.dayOfWeek(2)
-						.year(2020)
-						.build();
-
-		SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
-		Map<String, LocalDate> trainDates = releaseTrain.getTrainDates();
-		assertThat(trainDates).hasSize(5);
-		assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2020, 7, 14));
-		assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2020, 8, 11));
-		assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2020, 9, 15));
-		assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2020, 10, 13));
-		assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2020, 11, 10));
-	}
-
-	@Test
-	public void getTrainDatesWhenTrainOneIsSecondTuesdayOf2022ThenSuccess() {
-		SpringReleaseTrainSpec releaseTrainSpec =
-				SpringReleaseTrainSpec.builder()
-						.train(1)
-						.version("1.0.0")
-						.weekOfMonth(2)
-						.dayOfWeek(2)
-						.year(2022)
-						.build();
-
-		SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
-		Map<String, LocalDate> trainDates = releaseTrain.getTrainDates();
-		assertThat(trainDates).hasSize(5);
-		assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2022, 1, 11));
-		assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2022, 2, 15));
-		assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2022, 3, 15));
-		assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2022, 4, 12));
-		assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2022, 5, 10));
-	}
-
-	@Test
-	public void getTrainDatesWhenTrainTwoIsSecondTuesdayOf2022ThenSuccess() {
-		SpringReleaseTrainSpec releaseTrainSpec =
-				SpringReleaseTrainSpec.builder()
-						.train(2)
-						.version("1.0.0")
-						.weekOfMonth(2)
-						.dayOfWeek(2)
-						.year(2022)
-						.build();
-
-		SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
-		Map<String, LocalDate> trainDates = releaseTrain.getTrainDates();
-		assertThat(trainDates).hasSize(5);
-		assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2022, 7, 12));
-		assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2022, 8, 9));
-		assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2022, 9, 13));
-		assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2022, 10, 11));
-		assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2022, 11, 15));
-	}
-
-	@Test
-	public void getTrainDatesWhenTrainOneIsThirdMondayOf2022ThenSuccess() {
-		SpringReleaseTrainSpec releaseTrainSpec =
-				SpringReleaseTrainSpec.builder()
-						.train(1)
-						.version("1.0.0")
-						.weekOfMonth(3)
-						.dayOfWeek(1)
-						.year(2022)
-						.build();
-
-		SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
-		Map<String, LocalDate> trainDates = releaseTrain.getTrainDates();
-		assertThat(trainDates).hasSize(5);
-		assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2022, 1, 17));
-		assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2022, 2, 21));
-		assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2022, 3, 21));
-		assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2022, 4, 18));
-		assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2022, 5, 16));
-	}
-
-	@Test
-	public void getTrainDatesWhenTrainTwoIsThirdMondayOf2022ThenSuccess() {
-		SpringReleaseTrainSpec releaseTrainSpec =
-				SpringReleaseTrainSpec.builder()
-						.train(2)
-						.version("1.0.0")
-						.weekOfMonth(3)
-						.dayOfWeek(1)
-						.year(2022)
-						.build();
-
-		SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
-		Map<String, LocalDate> trainDates = releaseTrain.getTrainDates();
-		assertThat(trainDates).hasSize(5);
-		assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2022, 7, 18));
-		assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2022, 8, 15));
-		assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2022, 9, 19));
-		assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2022, 10, 17));
-		assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2022, 11, 21));
-	}
-
-	@Test
-	public void isTrainDateWhenTrainOneIsThirdMondayOf2022ThenSuccess() {
-		SpringReleaseTrainSpec releaseTrainSpec =
-				SpringReleaseTrainSpec.builder()
-						.train(1)
-						.version("1.0.0")
-						.weekOfMonth(3)
-						.dayOfWeek(1)
-						.year(2022)
-						.build();
-
-		SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
-		for (int dayOfMonth = 1; dayOfMonth <= 31; dayOfMonth++) {
-			assertThat(releaseTrain.isTrainDate("1.0.0-M1", LocalDate.of(2022, 1, dayOfMonth))).isEqualTo(dayOfMonth == 17);
-		}
-		for (int dayOfMonth = 1; dayOfMonth <= 28; dayOfMonth++) {
-			assertThat(releaseTrain.isTrainDate("1.0.0-M2", LocalDate.of(2022, 2, dayOfMonth))).isEqualTo(dayOfMonth == 21);
-		}
-		for (int dayOfMonth = 1; dayOfMonth <= 31; dayOfMonth++) {
-			assertThat(releaseTrain.isTrainDate("1.0.0-M3", LocalDate.of(2022, 3, dayOfMonth))).isEqualTo(dayOfMonth == 21);
-		}
-		for (int dayOfMonth = 1; dayOfMonth <= 30; dayOfMonth++) {
-			assertThat(releaseTrain.isTrainDate("1.0.0-RC1", LocalDate.of(2022, 4, dayOfMonth))).isEqualTo(dayOfMonth == 18);
-		}
-		for (int dayOfMonth = 1; dayOfMonth <= 31; dayOfMonth++) {
-			assertThat(releaseTrain.isTrainDate("1.0.0", LocalDate.of(2022, 5, dayOfMonth))).isEqualTo(dayOfMonth == 16);
-		}
-	}
-
-	@ParameterizedTest
-	@CsvSource({
-			"2022-01-01, 2022-02-21",
-			"2022-02-01, 2022-02-21",
-			"2022-02-21, 2022-04-18",
-			"2022-03-01, 2022-04-18",
-			"2022-04-01, 2022-04-18",
-			"2022-04-18, 2022-06-20",
-			"2022-05-01, 2022-06-20",
-			"2022-06-01, 2022-06-20",
-			"2022-06-20, 2022-08-15",
-			"2022-07-01, 2022-08-15",
-			"2022-08-01, 2022-08-15",
-			"2022-08-15, 2022-10-17",
-			"2022-09-01, 2022-10-17",
-			"2022-10-01, 2022-10-17",
-			"2022-10-17, 2022-12-19",
-			"2022-11-01, 2022-12-19",
-			"2022-12-01, 2022-12-19",
-			"2022-12-19, 2023-02-20"
-	})
-	public void getNextReleaseDateWhenBoundaryConditionsThenSuccess(LocalDate startDate, LocalDate expectedDate) {
-		SpringReleaseTrainSpec releaseTrainSpec =
-				SpringReleaseTrainSpec.builder()
-						.train(1)
-						.version("1.0.0")
-						.weekOfMonth(3)
-						.dayOfWeek(1)
-						.year(2022)
-						.build();
-
-		SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
-		assertThat(releaseTrain.getNextReleaseDate(startDate)).isEqualTo(expectedDate);
-	}
-}

+ 0 - 89
buildSrc/src/test/java/org/springframework/gradle/github/release/GitHubActionsApiTests.java

@@ -1,89 +0,0 @@
-/*
- * Copyright 2002-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.gradle.github.release;
-
-import java.nio.charset.Charset;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-import okhttp3.mockwebserver.MockResponse;
-import okhttp3.mockwebserver.MockWebServer;
-import okhttp3.mockwebserver.RecordedRequest;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import org.springframework.gradle.github.RepositoryRef;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-
-/**
- * @author Steve Riesenberg
- */
-public class GitHubActionsApiTests {
-	private GitHubActionsApi gitHubActionsApi;
-
-	private MockWebServer server;
-
-	private String baseUrl;
-
-	private RepositoryRef repository;
-
-	@BeforeEach
-	public void setup() throws Exception {
-		this.server = new MockWebServer();
-		this.server.start();
-		this.baseUrl = this.server.url("/api").toString();
-		this.gitHubActionsApi = new GitHubActionsApi("mock-oauth-token");
-		this.gitHubActionsApi.setBaseUrl(this.baseUrl);
-		this.repository = new RepositoryRef("spring-projects", "spring-security");
-	}
-
-	@AfterEach
-	public void cleanup() throws Exception {
-		this.server.shutdown();
-	}
-
-	@Test
-	public void dispatchWorkflowWhenValidParametersThenSuccess() throws Exception {
-		this.server.enqueue(new MockResponse().setResponseCode(204));
-
-		Map<String, Object> inputs = new LinkedHashMap<>();
-		inputs.put("input-1", "value");
-		inputs.put("input-2", false);
-		WorkflowDispatch workflowDispatch = new WorkflowDispatch("main", inputs);
-		this.gitHubActionsApi.dispatchWorkflow(this.repository, "test-workflow.yml", workflowDispatch);
-
-		RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
-		assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("post");
-		assertThat(recordedRequest.getRequestUrl().toString())
-				.isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/actions/workflows/test-workflow.yml/dispatches");
-		assertThat(recordedRequest.getBody().readString(Charset.defaultCharset()))
-				.isEqualTo("{\"ref\":\"main\",\"inputs\":{\"input-1\":\"value\",\"input-2\":false}}");
-	}
-
-	@Test
-	public void dispatchWorkflowWhenErrorResponseThenException() throws Exception {
-		this.server.enqueue(new MockResponse().setResponseCode(400));
-
-		WorkflowDispatch workflowDispatch = new WorkflowDispatch("main", null);
-		assertThatExceptionOfType(RuntimeException.class)
-				.isThrownBy(() -> this.gitHubActionsApi.dispatchWorkflow(this.repository, "test-workflow.yml", workflowDispatch));
-	}
-}

+ 0 - 155
buildSrc/src/test/java/org/springframework/gradle/github/release/GitHubReleaseApiTests.java

@@ -1,155 +0,0 @@
-/*
- * Copyright 2002-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.gradle.github.release;
-
-import java.nio.charset.Charset;
-import java.util.concurrent.TimeUnit;
-
-import okhttp3.mockwebserver.MockResponse;
-import okhttp3.mockwebserver.MockWebServer;
-import okhttp3.mockwebserver.RecordedRequest;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import org.springframework.gradle.github.RepositoryRef;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-
-/**
- * @author Steve Riesenberg
- */
-public class GitHubReleaseApiTests {
-	private GitHubReleaseApi gitHubReleaseApi;
-
-	private MockWebServer server;
-
-	private String baseUrl;
-
-	private RepositoryRef repository;
-
-	@BeforeEach
-	public void setup() throws Exception {
-		this.server = new MockWebServer();
-		this.server.start();
-		this.baseUrl = this.server.url("/api").toString();
-		this.gitHubReleaseApi = new GitHubReleaseApi("mock-oauth-token");
-		this.gitHubReleaseApi.setBaseUrl(this.baseUrl);
-		this.repository = new RepositoryRef("spring-projects", "spring-security");
-	}
-
-	@AfterEach
-	public void cleanup() throws Exception {
-		this.server.shutdown();
-	}
-
-	@Test
-	public void publishReleaseWhenValidParametersThenSuccess() throws Exception {
-		String responseJson = "{\n" +
-				"  \"url\": \"https://api.github.com/spring-projects/spring-security/releases/1\",\n" +
-				"  \"html_url\": \"https://github.com/spring-projects/spring-security/releases/tags/v1.0.0\",\n" +
-				"  \"assets_url\": \"https://api.github.com/spring-projects/spring-security/releases/1/assets\",\n" +
-				"  \"upload_url\": \"https://uploads.github.com/spring-projects/spring-security/releases/1/assets{?name,label}\",\n" +
-				"  \"tarball_url\": \"https://api.github.com/spring-projects/spring-security/tarball/v1.0.0\",\n" +
-				"  \"zipball_url\": \"https://api.github.com/spring-projects/spring-security/zipball/v1.0.0\",\n" +
-				"  \"discussion_url\": \"https://github.com/spring-projects/spring-security/discussions/90\",\n" +
-				"  \"id\": 1,\n" +
-				"  \"node_id\": \"MDc6UmVsZWFzZTE=\",\n" +
-				"  \"tag_name\": \"v1.0.0\",\n" +
-				"  \"target_commitish\": \"main\",\n" +
-				"  \"name\": \"v1.0.0\",\n" +
-				"  \"body\": \"Description of the release\",\n" +
-				"  \"draft\": false,\n" +
-				"  \"prerelease\": false,\n" +
-				"  \"created_at\": \"2013-02-27T19:35:32Z\",\n" +
-				"  \"published_at\": \"2013-02-27T19:35:32Z\",\n" +
-				"  \"author\": {\n" +
-				"    \"login\": \"sjohnr\",\n" +
-				"    \"id\": 1,\n" +
-				"    \"node_id\": \"MDQ6VXNlcjE=\",\n" +
-				"    \"avatar_url\": \"https://github.com/images/avatar.gif\",\n" +
-				"    \"gravatar_id\": \"\",\n" +
-				"    \"url\": \"https://api.github.com/users/sjohnr\",\n" +
-				"    \"html_url\": \"https://github.com/sjohnr\",\n" +
-				"    \"followers_url\": \"https://api.github.com/users/sjohnr/followers\",\n" +
-				"    \"following_url\": \"https://api.github.com/users/sjohnr/following{/other_user}\",\n" +
-				"    \"gists_url\": \"https://api.github.com/users/sjohnr/gists{/gist_id}\",\n" +
-				"    \"starred_url\": \"https://api.github.com/users/sjohnr/starred{/owner}{/repo}\",\n" +
-				"    \"subscriptions_url\": \"https://api.github.com/users/sjohnr/subscriptions\",\n" +
-				"    \"organizations_url\": \"https://api.github.com/users/sjohnr/orgs\",\n" +
-				"    \"repos_url\": \"https://api.github.com/users/sjohnr/repos\",\n" +
-				"    \"events_url\": \"https://api.github.com/users/sjohnr/events{/privacy}\",\n" +
-				"    \"received_events_url\": \"https://api.github.com/users/sjohnr/received_events\",\n" +
-				"    \"type\": \"User\",\n" +
-				"    \"site_admin\": false\n" +
-				"  },\n" +
-				"  \"assets\": [\n" +
-				"    {\n" +
-				"      \"url\": \"https://api.github.com/spring-projects/spring-security/releases/assets/1\",\n" +
-				"      \"browser_download_url\": \"https://github.com/spring-projects/spring-security/releases/download/v1.0.0/example.zip\",\n" +
-				"      \"id\": 1,\n" +
-				"      \"node_id\": \"MDEyOlJlbGVhc2VBc3NldDE=\",\n" +
-				"      \"name\": \"example.zip\",\n" +
-				"      \"label\": \"short description\",\n" +
-				"      \"state\": \"uploaded\",\n" +
-				"      \"content_type\": \"application/zip\",\n" +
-				"      \"size\": 1024,\n" +
-				"      \"download_count\": 42,\n" +
-				"      \"created_at\": \"2013-02-27T19:35:32Z\",\n" +
-				"      \"updated_at\": \"2013-02-27T19:35:32Z\",\n" +
-				"      \"uploader\": {\n" +
-				"        \"login\": \"sjohnr\",\n" +
-				"        \"id\": 1,\n" +
-				"        \"node_id\": \"MDQ6VXNlcjE=\",\n" +
-				"        \"avatar_url\": \"https://github.com/images/avatar.gif\",\n" +
-				"        \"gravatar_id\": \"\",\n" +
-				"        \"url\": \"https://api.github.com/users/sjohnr\",\n" +
-				"        \"html_url\": \"https://github.com/sjohnr\",\n" +
-				"        \"followers_url\": \"https://api.github.com/users/sjohnr/followers\",\n" +
-				"        \"following_url\": \"https://api.github.com/users/sjohnr/following{/other_user}\",\n" +
-				"        \"gists_url\": \"https://api.github.com/users/sjohnr/gists{/gist_id}\",\n" +
-				"        \"starred_url\": \"https://api.github.com/users/sjohnr/starred{/owner}{/repo}\",\n" +
-				"        \"subscriptions_url\": \"https://api.github.com/users/sjohnr/subscriptions\",\n" +
-				"        \"organizations_url\": \"https://api.github.com/users/sjohnr/orgs\",\n" +
-				"        \"repos_url\": \"https://api.github.com/users/sjohnr/repos\",\n" +
-				"        \"events_url\": \"https://api.github.com/users/sjohnr/events{/privacy}\",\n" +
-				"        \"received_events_url\": \"https://api.github.com/users/sjohnr/received_events\",\n" +
-				"        \"type\": \"User\",\n" +
-				"        \"site_admin\": false\n" +
-				"      }\n" +
-				"    }\n" +
-				"  ]\n" +
-				"}";
-		this.server.enqueue(new MockResponse().setBody(responseJson));
-		this.gitHubReleaseApi.publishRelease(this.repository, Release.tag("1.0.0").build());
-
-		RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
-		assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("post");
-		assertThat(recordedRequest.getRequestUrl().toString())
-				.isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/releases");
-		assertThat(recordedRequest.getBody().readString(Charset.defaultCharset()))
-				.isEqualTo("{\"tag_name\":\"1.0.0\",\"draft\":false,\"prerelease\":false,\"generate_release_notes\":false}");
-	}
-
-	@Test
-	public void publishReleaseWhenErrorResponseThenException() throws Exception {
-		this.server.enqueue(new MockResponse().setResponseCode(400));
-		assertThatExceptionOfType(RuntimeException.class)
-				.isThrownBy(() -> this.gitHubReleaseApi.publishRelease(this.repository, Release.tag("1.0.0").build()));
-	}
-}

+ 0 - 106
buildSrc/src/test/java/org/springframework/gradle/github/user/GitHubUserApiTests.java

@@ -1,106 +0,0 @@
-/*
- * Copyright 2020-2023 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.gradle.github.user;
-
-import okhttp3.mockwebserver.MockResponse;
-import okhttp3.mockwebserver.MockWebServer;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-
-/**
- * @author Steve Riesenberg
- */
-public class GitHubUserApiTests {
-	private GitHubUserApi gitHubUserApi;
-
-	private MockWebServer server;
-
-	private String baseUrl;
-
-	@BeforeEach
-	public void setup() throws Exception {
-		this.server = new MockWebServer();
-		this.server.start();
-		this.baseUrl = this.server.url("/api").toString();
-		this.gitHubUserApi = new GitHubUserApi("mock-oauth-token");
-		this.gitHubUserApi.setBaseUrl(this.baseUrl);
-	}
-
-	@AfterEach
-	public void cleanup() throws Exception {
-		this.server.shutdown();
-	}
-
-	@Test
-	public void getUserWhenValidParametersThenSuccess() {
-		// @formatter:off
-		String responseJson = "{\n" +
-				"    \"avatar_url\": \"https://avatars.githubusercontent.com/u/583231?v=4\",\n" +
-				"    \"bio\": null,\n" +
-				"    \"blog\": \"https://github.blog\",\n" +
-				"    \"company\": \"@github\",\n" +
-				"    \"created_at\": \"2011-01-25T18:44:36Z\",\n" +
-				"    \"email\": null,\n" +
-				"    \"events_url\": \"https://api.github.com/users/octocat/events{/privacy}\",\n" +
-				"    \"followers\": 8481,\n" +
-				"    \"followers_url\": \"https://api.github.com/users/octocat/followers\",\n" +
-				"    \"following\": 9,\n" +
-				"    \"following_url\": \"https://api.github.com/users/octocat/following{/other_user}\",\n" +
-				"    \"gists_url\": \"https://api.github.com/users/octocat/gists{/gist_id}\",\n" +
-				"    \"gravatar_id\": \"\",\n" +
-				"    \"hireable\": null,\n" +
-				"    \"html_url\": \"https://github.com/octocat\",\n" +
-				"    \"id\": 583231,\n" +
-				"    \"location\": \"San Francisco\",\n" +
-				"    \"login\": \"octocat\",\n" +
-				"    \"name\": \"The Octocat\",\n" +
-				"    \"node_id\": \"MDQ6VXNlcjU4MzIzMQ==\",\n" +
-				"    \"organizations_url\": \"https://api.github.com/users/octocat/orgs\",\n" +
-				"    \"public_gists\": 8,\n" +
-				"    \"public_repos\": 8,\n" +
-				"    \"received_events_url\": \"https://api.github.com/users/octocat/received_events\",\n" +
-				"    \"repos_url\": \"https://api.github.com/users/octocat/repos\",\n" +
-				"    \"site_admin\": false,\n" +
-				"    \"starred_url\": \"https://api.github.com/users/octocat/starred{/owner}{/repo}\",\n" +
-				"    \"subscriptions_url\": \"https://api.github.com/users/octocat/subscriptions\",\n" +
-				"    \"twitter_username\": null,\n" +
-				"    \"type\": \"User\",\n" +
-				"    \"updated_at\": \"2023-02-25T12:14:58Z\",\n" +
-				"    \"url\": \"https://api.github.com/users/octocat\"\n" +
-				"}";
-		// @formatter:on
-		this.server.enqueue(new MockResponse().setBody(responseJson));
-
-		User user = this.gitHubUserApi.getUser();
-		assertThat(user.getId()).isEqualTo(583231);
-		assertThat(user.getLogin()).isEqualTo("octocat");
-		assertThat(user.getName()).isEqualTo("The Octocat");
-		assertThat(user.getUrl()).isEqualTo("https://api.github.com/users/octocat");
-	}
-
-	@Test
-	public void getUserWhenErrorResponseThenException() {
-		this.server.enqueue(new MockResponse().setResponseCode(400));
-		// @formatter:off
-		assertThatExceptionOfType(RuntimeException.class)
-				.isThrownBy(() -> this.gitHubUserApi.getUser());
-		// @formatter:on
-	}
-}

+ 1 - 0
gradle/libs.versions.toml

@@ -35,6 +35,7 @@ io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javafo
 io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" }
 io-spring-nohttp-nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version.ref = "io-spring-nohttp" }
 io-spring-nohttp-nohttp-gradle = { module = "io.spring.nohttp:nohttp-gradle", version.ref = "io-spring-nohttp" }
+io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.1"
 jakarta-annotation-jakarta-annotation-api = "jakarta.annotation:jakarta.annotation-api:2.1.1"
 jakarta-inject-jakarta-inject-api = "jakarta.inject:jakarta.inject-api:2.0.1"
 jakarta-persistence-jakarta-persistence-api = "jakarta.persistence:jakarta.persistence-api:3.1.0"