Переглянути джерело

Add updateDependencies

Allows for updating the depencencies of the project in an automated fashion.

Closes gh-9542
Rob Winch 4 роки тому
батько
коміт
f6a5b723cb

+ 23 - 1
build.gradle

@@ -16,6 +16,7 @@ apply plugin: 'io.spring.nohttp'
 apply plugin: 'locks'
 apply plugin: 'io.spring.convention.root'
 apply plugin: 'org.jetbrains.kotlin.jvm'
+apply plugin: 'org.springframework.security.update-dependencies'
 
 group = 'org.springframework.security'
 description = 'Spring Security'
@@ -28,6 +29,26 @@ repositories {
 	mavenCentral()
 }
 
+updateDependenciesSettings {
+	gitHub {
+		organization = "rwinch"
+		repository = "spring-security"
+	}
+	addFiles({
+		return [
+				project.file("buildSrc/src/main/java/io/spring/gradle/convention/AsciidoctorConventionPlugin.java"),
+				project.file("buildSrc/src/main/groovy/io/spring/gradle/convention/CheckstylePlugin.groovy")
+		]
+	})
+	dependencyExcludes {
+		majorVersionBump()
+		alphaBetaVersions()
+		releaseCandidatesVersions()
+		milestoneVersions()
+		snapshotVersions()
+	}
+}
+
 subprojects {
 	plugins.withType(JavaPlugin) {
 		project.sourceCompatibility='1.8'
@@ -37,6 +58,7 @@ subprojects {
 	}
 }
 
+
 allprojects {
 	if (!['spring-security-bom', 'spring-security-docs'].contains(project.name)) {
 		apply plugin: 'io.spring.javaformat'
@@ -73,5 +95,5 @@ if (hasProperty('buildScan')) {
 nohttp {
 	allowlistFile = project.file("etc/nohttp/allowlist.lines")
 	source.exclude "buildSrc/build/**"
-	
+
 }

+ 15 - 4
buildSrc/build.gradle

@@ -1,6 +1,10 @@
-apply plugin: "java-gradle-plugin"
-apply plugin: 'java'
-apply plugin: 'groovy'
+plugins {
+	id "java-gradle-plugin"
+	id "java"
+	id "groovy"
+	id 'com.apollographql.apollo' version '2.4.5'
+}
+
 
 sourceCompatibility = 1.8
 
@@ -25,6 +29,10 @@ gradlePlugin {
 			id = "io.spring.convention.management-configuration"
 			implementationClass = "io.spring.gradle.convention.ManagementConfigurationPlugin"
 		}
+		updateDependencies {
+			id = "org.springframework.security.update-dependencies"
+			implementationClass = "org.springframework.security.convention.versions.UpdateDependenciesPlugin"
+		}
 	}
 }
 
@@ -39,7 +47,10 @@ dependencies {
 	implementation 'net.sourceforge.saxon:saxon:9.1.0.8'
 	implementation localGroovy()
 
-	implementation 'com.github.ben-manes:gradle-versions-plugin:0.25.0'
+	implementation 'io.projectreactor:reactor-core:3.4.4'
+	implementation 'gradle.plugin.org.gretty:gretty:3.0.1'
+	implementation 'com.apollographql.apollo:apollo-runtime:2.4.5'
+	implementation 'com.github.ben-manes:gradle-versions-plugin:0.38.0'
 	implementation 'io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.21.1'
 	implementation 'io.spring.gradle:docbook-reference-plugin:0.3.1'
 	implementation 'io.spring.gradle:propdeps-plugin:0.0.10.RELEASE'

+ 7 - 0
buildSrc/src/main/graphql/org/springframework/security/convention/versions/CreateIssue.graphql

@@ -0,0 +1,7 @@
+mutation CreateIssueInput($assigneeId: ID!, $labelIds: [ID!], $milestoneId: ID!, $repositoryId: ID!, $title: String!) {
+    createIssue(input: {assigneeIds: [$assigneeId], labelIds: $labelIds, milestoneId: $milestoneId, projectIds: [], repositoryId: $repositoryId, title: $title}) {
+        issue {
+            number
+        }
+    }
+}

+ 20 - 0
buildSrc/src/main/graphql/org/springframework/security/convention/versions/FindCreateIssueInput.graphql

@@ -0,0 +1,20 @@
+query FindCreateIssueInput($owner: String!, $name: String!, $labelQuery: String, $milestoneName: String) {
+  repository(owner: $owner, name: $name) {
+      id
+      labels(query: $labelQuery, first: 1) {
+          nodes {
+              id
+              name
+          }
+      }
+      milestones(query: $milestoneName, states: [OPEN], first: 1) {
+          nodes {
+              id
+              title
+          }
+      }
+  }
+  viewer {
+      id
+  }
+}

+ 8 - 0
buildSrc/src/main/graphql/org/springframework/security/convention/versions/RateLimit.graphql

@@ -0,0 +1,8 @@
+query RateLimit {
+    rateLimit {
+        limit
+        cost
+        remaining
+        resetAt
+    }
+}

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
buildSrc/src/main/graphql/org/springframework/security/convention/versions/schema.json


+ 0 - 1
buildSrc/src/main/groovy/io/spring/gradle/convention/AbstractSpringJavaPlugin.groovy

@@ -59,7 +59,6 @@ public abstract class AbstractSpringJavaPlugin implements Plugin<Project> {
 		pluginManager.apply("io.spring.convention.dependency-set");
 		pluginManager.apply("io.spring.convention.javadoc-options");
 		pluginManager.apply("io.spring.convention.checkstyle");
-		pluginManager.apply('com.github.ben-manes.versions');
 
 		copyPropertyFromRootProjectTo("group", project);
 		copyPropertyFromRootProjectTo("version", project);

+ 179 - 0
buildSrc/src/main/java/org/springframework/security/convention/versions/GitHubApi.java

@@ -0,0 +1,179 @@
+package org.springframework.security.convention.versions;
+
+import com.apollographql.apollo.ApolloCall;
+import com.apollographql.apollo.ApolloClient;
+import com.apollographql.apollo.api.Input;
+import com.apollographql.apollo.api.Response;
+import com.apollographql.apollo.exception.ApolloException;
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import org.jetbrains.annotations.NotNull;
+import reactor.core.publisher.Mono;
+import reactor.util.retry.Retry;
+import reactor.util.retry.RetrySpec;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+public class GitHubApi {
+
+	private final ApolloClient apolloClient;
+
+	public GitHubApi(String githubToken) {
+		if (githubToken == null) {
+			throw new IllegalArgumentException("githubToken is required. You can set it using -PgitHubAccessToken=");
+		}
+		OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
+		clientBuilder.addInterceptor(new AuthorizationInterceptor(githubToken));
+		this.apolloClient = ApolloClient.builder()
+				.serverUrl("https://api.github.com/graphql")
+				.okHttpClient(clientBuilder.build())
+				.build();
+	}
+
+	public Mono<FindCreateIssueResult> findCreateIssueInput(String owner, String name, String milestone) {
+		String label = "\"type: dependency-upgrade\"";
+		FindCreateIssueInputQuery findCreateIssueInputQuery = new FindCreateIssueInputQuery(owner, name, Input.optional(label), Input.optional(milestone));
+		return Mono.create( sink -> this.apolloClient.query(findCreateIssueInputQuery)
+				.enqueue(new ApolloCall.Callback<FindCreateIssueInputQuery.Data>() {
+					@Override
+					public void onResponse(@NotNull Response<FindCreateIssueInputQuery.Data> response) {
+						if (response.hasErrors()) {
+							sink.error(new RuntimeException(response.getErrors().stream().map(e -> e.getMessage()).collect(Collectors.joining(" "))));
+						} else {
+							FindCreateIssueInputQuery.Data data = response.getData();
+							FindCreateIssueInputQuery.Repository repository = data.repository();
+							List<String> labels = repository.labels().nodes().stream().map(FindCreateIssueInputQuery.Node::id).collect(Collectors.toList());
+							if (labels.isEmpty()) {
+								sink.error(new IllegalArgumentException("Could not find label for " + label));
+								return;
+							}
+							Optional<String> firstMilestoneId = repository.milestones().nodes().stream().map(FindCreateIssueInputQuery.Node1::id).findFirst();
+							if (!firstMilestoneId.isPresent()) {
+								sink.error(new IllegalArgumentException("Could not find OPEN milestone id for " + milestone));
+								return;
+							}
+							String milestoneId = firstMilestoneId.get();
+							String repositoryId = repository.id();
+							String assigneeId = data.viewer().id();
+							sink.success(new FindCreateIssueResult(repositoryId, labels, milestoneId, assigneeId));
+						}
+					}
+					@Override
+					public void onFailure(@NotNull ApolloException e) {
+						sink.error(e);
+					}
+				}));
+	}
+
+	public static class FindCreateIssueResult {
+		private final String repositoryId;
+		private final List<String> labelIds;
+		private final String milestoneId;
+		private final String assigneeId;
+
+		public FindCreateIssueResult(String repositoryId, List<String> labelIds, String milestoneId, String assigneeId) {
+			this.repositoryId = repositoryId;
+			this.labelIds = labelIds;
+			this.milestoneId = milestoneId;
+			this.assigneeId = assigneeId;
+		}
+
+		public String getRepositoryId() {
+			return repositoryId;
+		}
+
+		public List<String> getLabelIds() {
+			return labelIds;
+		}
+
+		public String getMilestoneId() {
+			return milestoneId;
+		}
+
+		public String getAssigneeId() {
+			return assigneeId;
+		}
+	}
+
+	public Mono<RateLimitQuery.RateLimit> findRateLimit() {
+		return Mono.create( sink -> this.apolloClient.query(new RateLimitQuery())
+			.enqueue(new ApolloCall.Callback<RateLimitQuery.Data>() {
+				@Override
+				public void onResponse(@NotNull Response<RateLimitQuery.Data> response) {
+					if (response.hasErrors()) {
+						sink.error(new RuntimeException(response.getErrors().stream().map(e -> e.getMessage()).collect(Collectors.joining(" "))));
+					} else {
+						sink.success(response.getData().rateLimit());
+					}
+				}
+				@Override
+				public void onFailure(@NotNull ApolloException e) {
+					sink.error(e);
+				}
+			}));
+	}
+
+	public Mono<Integer> createIssue(String repositoryId, String title, List<String> labelIds, String milestoneId, String assigneeId) {
+		CreateIssueInputMutation createIssue = new CreateIssueInputMutation.Builder()
+				.repositoryId(repositoryId)
+				.title(title)
+				.labelIds(labelIds)
+				.milestoneId(milestoneId)
+				.assigneeId(assigneeId)
+				.build();
+		return Mono.create( sink -> this.apolloClient.mutate(createIssue)
+				.enqueue(new ApolloCall.Callback<CreateIssueInputMutation.Data>() {
+					@Override
+					public void onResponse(@NotNull Response<CreateIssueInputMutation.Data> response) {
+						if (response.hasErrors()) {
+							String message = response.getErrors().stream().map(e -> e.getMessage() + " " + e.getCustomAttributes() + " " + e.getLocations()).collect(Collectors.joining(" "));
+							if (message.contains("was submitted too quickly")) {
+								sink.error(new SubmittedTooQuick(message));
+							} else {
+								sink.error(new RuntimeException(message));
+							}
+						} else {
+							sink.success(response.getData().createIssue().issue().number());
+						}
+					}
+					@Override
+					public void onFailure(@NotNull ApolloException e) {
+						sink.error(e);
+					}
+				}))
+				.retryWhen(
+					RetrySpec.fixedDelay(3, Duration.ofMinutes(1))
+						.filter(SubmittedTooQuick.class::isInstance)
+						.doBeforeRetry(r -> System.out.println("Pausing for 1 minute and then retrying due to receiving \"submitted too quickly\" error from GitHub API"))
+				)
+				.cast(Integer.class);
+	}
+
+	public static class SubmittedTooQuick extends RuntimeException {
+		public SubmittedTooQuick(String message) {
+			super(message);
+		}
+	}
+
+	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);
+		}
+	}
+}

+ 168 - 0
buildSrc/src/main/java/org/springframework/security/convention/versions/UpdateDependenciesExtension.java

@@ -0,0 +1,168 @@
+package org.springframework.security.convention.versions;
+
+import com.github.benmanes.gradle.versions.updates.resolutionstrategy.ComponentSelectionWithCurrent;
+import org.gradle.api.Action;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+
+public class UpdateDependenciesExtension {
+	private Supplier<List<File>> files;
+
+	private UpdateMode updateMode = UpdateMode.COMMIT;
+
+	private DependencyExcludes dependencyExcludes = new DependencyExcludes();
+
+	private GitHub gitHub = new GitHub();
+
+	public UpdateDependenciesExtension(Supplier<List<File>> files) {
+		this.files = files;
+	}
+
+	public void setUpdateMode(UpdateMode updateMode) {
+		this.updateMode = updateMode;
+	}
+
+	public UpdateMode getUpdateMode() {
+		return updateMode;
+	}
+
+	GitHub getGitHub() {
+		return this.gitHub;
+	}
+
+	DependencyExcludes getExcludes() {
+		return dependencyExcludes;
+	}
+
+	Supplier<List<File>> getFiles() {
+		return files;
+	}
+
+	public void setFiles(Supplier<List<File>> files) {
+		this.files = files;
+	}
+
+	public void addFiles(Supplier<List<File>> files) {
+		Supplier<List<File>> original = this.files;
+		setFiles(() -> {
+			List<File> result = new ArrayList<>(original.get());
+			result.addAll(files.get());
+			return result;
+		});
+	}
+
+	public void dependencyExcludes(Action<DependencyExcludes> excludes) {
+		excludes.execute(this.dependencyExcludes);
+	}
+
+	public void gitHub(Action<GitHub> gitHub) {
+		gitHub.execute(this.gitHub);
+	}
+
+	public enum UpdateMode {
+		COMMIT,
+		GITHUB_ISSUE
+	}
+
+	public class GitHub {
+		private String organization;
+
+		private String repository;
+
+		private String accessToken;
+
+		private String milestone;
+
+		public String getOrganization() {
+			return organization;
+		}
+
+		public void setOrganization(String organization) {
+			this.organization = organization;
+		}
+
+		public String getRepository() {
+			return repository;
+		}
+
+		public void setRepository(String repository) {
+			this.repository = repository;
+		}
+
+		public String getAccessToken() {
+			return accessToken;
+		}
+
+		public void setAccessToken(String accessToken) {
+			this.accessToken = accessToken;
+		}
+
+		public String getMilestone() {
+			return milestone;
+		}
+
+		public void setMilestone(String milestone) {
+			this.milestone = milestone;
+		}
+	}
+
+	/**
+	 * Consider creating some Predicates instead since they are composible
+	 */
+	public class DependencyExcludes {
+		private List<Action<ComponentSelectionWithCurrent>> actions = new ArrayList<>();
+
+		List<Action<ComponentSelectionWithCurrent>> getActions() {
+			return actions;
+		}
+
+		public DependencyExcludes alphaBetaVersions() {
+			this.actions.add(excludeVersionWithRegex("(?i).*?(alpha|beta).*", "an alpha or beta version"));
+			return this;
+		}
+
+		public DependencyExcludes majorVersionBump() {
+			this.actions.add((selection) -> {
+				String currentVersion = selection.getCurrentVersion();
+				int separator = currentVersion.indexOf(".");
+				String major = separator > 0 ? currentVersion.substring(0, separator) : currentVersion;
+				String candidateVersion = selection.getCandidate().getVersion();
+				Pattern calVerPattern = Pattern.compile("\\d\\d\\d\\d.*");
+				boolean isCalVer = calVerPattern.matcher(candidateVersion).matches();
+				if (!isCalVer && !candidateVersion.startsWith(major)) {
+					selection.reject("Cannot upgrade to new Major Version");
+				}
+			});
+			return this;
+		}
+
+		public DependencyExcludes releaseCandidatesVersions() {
+			this.actions.add(excludeVersionWithRegex("(?i).*?rc\\d+.*", "a release candidate version"));
+			return this;
+		}
+
+		public DependencyExcludes milestoneVersions() {
+			this.actions.add(excludeVersionWithRegex("(?i).*?m\\d+.*", "a milestone version"));
+			return this;
+		}
+
+		public DependencyExcludes snapshotVersions() {
+			this.actions.add(excludeVersionWithRegex(".*?-SNAPSHOT.*", "a SNAPSHOT version"));
+			return this;
+		}
+
+		private Action<ComponentSelectionWithCurrent> excludeVersionWithRegex(String regex, String reason) {
+			Pattern pattern = Pattern.compile(regex);
+			return (selection) -> {
+				String candidateVersion = selection.getCandidate().getVersion();
+				if (pattern.matcher(candidateVersion).matches()) {
+					selection.reject(candidateVersion + " is not allowed because it is " + reason);
+				}
+			};
+		}
+	}
+}

+ 254 - 0
buildSrc/src/main/java/org/springframework/security/convention/versions/UpdateDependenciesPlugin.java

@@ -0,0 +1,254 @@
+package org.springframework.security.convention.versions;
+
+import com.github.benmanes.gradle.versions.reporter.result.DependencyOutdated;
+import com.github.benmanes.gradle.versions.reporter.result.Result;
+import com.github.benmanes.gradle.versions.reporter.result.VersionAvailable;
+import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask;
+import com.github.benmanes.gradle.versions.updates.gradle.GradleUpdateResult;
+import com.github.benmanes.gradle.versions.updates.resolutionstrategy.ComponentSelectionRulesWithCurrent;
+import com.github.benmanes.gradle.versions.updates.resolutionstrategy.ComponentSelectionWithCurrent;
+import com.github.benmanes.gradle.versions.updates.resolutionstrategy.ResolutionStrategyWithCurrent;
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
+import reactor.core.publisher.Mono;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.time.Duration;
+import java.util.*;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class UpdateDependenciesPlugin implements Plugin<Project> {
+	private GitHubApi gitHubApi;
+
+	@Override
+	public void apply(Project project) {
+		UpdateDependenciesExtension updateDependenciesSettings = project.getExtensions().create("updateDependenciesSettings", UpdateDependenciesExtension.class, defaultFiles(project));
+		if (project.hasProperty("updateMode")) {
+			String updateMode = String.valueOf(project.findProperty("updateMode"));
+			updateDependenciesSettings.setUpdateMode(UpdateDependenciesExtension.UpdateMode.valueOf(updateMode));
+		}
+		if (project.hasProperty("nextVersion")) {
+			String nextVersion = String.valueOf(project.findProperty("nextVersion"));
+			updateDependenciesSettings.getGitHub().setMilestone(nextVersion);
+		}
+		if (project.hasProperty("gitHubAccessToken")) {
+			String gitHubAccessToken = String.valueOf(project.findProperty("gitHubAccessToken"));
+			updateDependenciesSettings.getGitHub().setAccessToken(gitHubAccessToken);
+		}
+		project.getTasks().register("updateDependencies", DependencyUpdatesTask.class, new Action<DependencyUpdatesTask>() {
+			@Override
+			public void execute(DependencyUpdatesTask updateDependencies) {
+				updateDependencies.setDescription("Update the dependencies");
+				updateDependencies.setCheckConstraints(true);
+				updateDependencies.setOutputFormatter(new Closure<Void>(null) {
+					@Override
+					public Void call(Object argument) {
+						Result result = (Result) argument;
+						if (gitHubApi == null && updateDependenciesSettings.getUpdateMode() != UpdateDependenciesExtension.UpdateMode.COMMIT) {
+							gitHubApi = new GitHubApi(updateDependenciesSettings.getGitHub().getAccessToken());
+						}
+						updateDependencies(result, project, updateDependenciesSettings);
+						updateGradleVersion(result, project, updateDependenciesSettings);
+						return null;
+					}
+				});
+				updateDependencies.resolutionStrategy(new Action<ResolutionStrategyWithCurrent>() {
+					@Override
+					public void execute(ResolutionStrategyWithCurrent resolution) {
+						resolution.componentSelection(new Action<ComponentSelectionRulesWithCurrent>() {
+							@Override
+							public void execute(ComponentSelectionRulesWithCurrent components) {
+								updateDependenciesSettings.getExcludes().getActions().forEach((action) -> {
+									components.all(action);
+								});
+								components.all((selection) -> {
+									ModuleComponentIdentifier candidate = selection.getCandidate();
+									if (candidate.getGroup().startsWith("org.apache.directory.") && !candidate.getVersion().equals(selection.getCurrentVersion())) {
+										selection.reject("org.apache.directory.* has breaking changes in newer versions");
+									}
+								});
+								String jaxbBetaRegex = ".*?b\\d+.*";
+								components.withModule("javax.xml.bind:jaxb-api", excludeWithRegex(jaxbBetaRegex, "Reject jaxb-api beta versions"));
+								components.withModule("com.sun.xml.bind:jaxb-impl", excludeWithRegex(jaxbBetaRegex, "Reject jaxb-api beta versions"));
+								components.withModule("commons-collections:commons-collections", excludeWithRegex("^\\d{3,}.*", "Reject commons-collections date based releases"));
+							}
+						});
+					}
+				});
+			}
+		});
+	}
+
+	private void updateDependencies(Result result, Project project, UpdateDependenciesExtension updateDependenciesSettings) {
+		SortedSet<DependencyOutdated> dependencies = result.getOutdated().getDependencies();
+		if (dependencies.isEmpty()) {
+			return;
+		}
+		Map<String, List<DependencyOutdated>> groups = new LinkedHashMap<>();
+		dependencies.forEach(outdated -> {
+			groups.computeIfAbsent(outdated.getGroup(), (key) -> new ArrayList<>()).add(outdated);
+		});
+		File gradlePropertiesFile = project.getRootProject().file(Project.GRADLE_PROPERTIES);
+		Mono<GitHubApi.FindCreateIssueResult> createIssueResult = createIssueResultMono(updateDependenciesSettings);
+		List<File> filesWithDependencies = updateDependenciesSettings.getFiles().get();
+		groups.forEach((group, outdated) -> {
+			outdated.forEach((dependency) -> {
+				String ga = dependency.getGroup() + ":" + dependency.getName() + ":";
+				String originalDependency = ga + dependency.getVersion();
+				String replacementDependency = ga + updatedVersion(dependency);
+				System.out.println("Update " + originalDependency + " to " + replacementDependency);
+				filesWithDependencies.forEach((fileWithDependency) -> {
+					updateDependencyInlineVersion(fileWithDependency, dependency);
+					updateDependencyWithVersionVariable(fileWithDependency, gradlePropertiesFile, dependency);
+				});
+			});
+
+			// commit
+			DependencyOutdated firstDependency = outdated.get(0);
+			String updatedVersion = updatedVersion(firstDependency);
+			String title = outdated.size() == 1 ? "Update " + firstDependency.getName() + " to " + updatedVersion : "Update " + firstDependency.getGroup() + " to " + updatedVersion;
+			afterGroup(updateDependenciesSettings, project.getRootDir(), title, createIssueResult);
+		});
+	}
+
+	private void afterGroup(UpdateDependenciesExtension updateDependenciesExtension, File rootDir, String title, Mono<GitHubApi.FindCreateIssueResult> createIssueResultMono) {
+
+		String commitMessage = title;
+		if (updateDependenciesExtension.getUpdateMode() == UpdateDependenciesExtension.UpdateMode.GITHUB_ISSUE) {
+			GitHubApi.FindCreateIssueResult createIssueResult = createIssueResultMono.block();
+			RateLimitQuery.RateLimit rateLimit = gitHubApi.findRateLimit().block();
+			rateLimit = gitHubApi.findRateLimit().block();
+			System.out.println("remaining " + rateLimit.remaining() + " reset at " + rateLimit.resetAt());
+			Integer issueNumber = gitHubApi.createIssue(createIssueResult.getRepositoryId(), title, createIssueResult.getLabelIds(), createIssueResult.getMilestoneId(), createIssueResult.getAssigneeId()).delayElement(Duration.ofSeconds(1)).block();
+			commitMessage += "\n\nCloses gh-" + issueNumber;
+		}
+		runCommand(rootDir, "git", "commit", "-am", commitMessage);
+	}
+
+	private Mono<GitHubApi.FindCreateIssueResult> createIssueResultMono(UpdateDependenciesExtension updateDependenciesExtension) {
+		return Mono.defer(() -> {
+			UpdateDependenciesExtension.GitHub gitHub = updateDependenciesExtension.getGitHub();
+			return gitHubApi.findCreateIssueInput(gitHub.getOrganization(), gitHub.getRepository(), gitHub.getMilestone()).cache();
+		});
+	}
+
+	private void updateGradleVersion(Result result, Project project, UpdateDependenciesExtension updateDependenciesSettings) {
+		GradleUpdateResult current = result.getGradle().getCurrent();
+		GradleUpdateResult running = result.getGradle().getRunning();
+		if (current.compareTo(running) > 0) {
+			String title = "Update Gradle to " + current.getVersion();
+			System.out.println(title);
+			runCommand(project.getRootDir(), "./gradlew", "wrapper", "--gradle-version", current.getVersion(), "--no-daemon");
+			afterGroup(updateDependenciesSettings, project.getRootDir(), title, createIssueResultMono(updateDependenciesSettings));
+		}
+	}
+
+	private static Supplier<List<File>> defaultFiles(Project project) {
+		return () -> {
+			List<File> result = new ArrayList<>();
+			result.add(project.getBuildFile());
+			project.getChildProjects().values().forEach((childProject) ->
+					result.add(childProject.getBuildFile())
+			);
+			result.add(project.getRootProject().file("buildSrc/build.gradle"));
+			return result;
+		};
+	}
+
+	static void runCommand(File dir, String... args) {
+		try {
+			Process process = new ProcessBuilder()
+					.directory(dir)
+					.command(args)
+					.start();
+			writeLinesTo(process.getInputStream(), System.out);
+			writeLinesTo(process.getErrorStream(), System.out);
+			if (process.waitFor() != 0) {
+				new RuntimeException("Failed to run " + Arrays.toString(args));
+			}
+		} catch (IOException | InterruptedException e) {
+			throw new RuntimeException("Failed to run " + Arrays.toString(args), e);
+		}
+	}
+
+	static void writeLinesTo(InputStream input, PrintStream out) {
+		Scanner scanner = new Scanner(input);
+		while(scanner.hasNextLine()) {
+			out.println(scanner.nextLine());
+		}
+	}
+
+
+	static Action<ComponentSelectionWithCurrent> excludeWithRegex(String regex, String reason) {
+		Pattern pattern = Pattern.compile(regex);
+		return (selection) -> {
+			String candidateVersion = selection.getCandidate().getVersion();
+			if (pattern.matcher(candidateVersion).matches()) {
+				selection.reject(candidateVersion + " is not allowed because it is " + reason);
+			}
+		};
+	}
+
+	static void updateDependencyInlineVersion(File buildFile, DependencyOutdated dependency){
+		String ga = dependency.getGroup() + ":" + dependency.getName() + ":";
+		String originalDependency = ga + dependency.getVersion();
+		String replacementDependency = ga + updatedVersion(dependency);
+		replaceFileText(buildFile, buildFileText -> buildFileText.replace(originalDependency, replacementDependency));
+	}
+
+	static void replaceFileText(File file, Function<String, String> replaceText) {
+		String buildFileText = readString(file);
+		String updatedBuildFileText = replaceText.apply(buildFileText);
+		writeString(file, updatedBuildFileText);
+	}
+
+	private static String readString(File file) {
+		try {
+			byte[] bytes = Files.readAllBytes(file.toPath());
+			return new String(bytes);
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private static void writeString(File file, String text) {
+		try {
+			Files.write(file.toPath(), text.getBytes());
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	static void updateDependencyWithVersionVariable(File scanFile, File gradlePropertiesFile, DependencyOutdated dependency) {
+		if (!gradlePropertiesFile.exists()) {
+			return;
+		}
+		replaceFileText(gradlePropertiesFile, (gradlePropertiesText) -> {
+			String ga = dependency.getGroup() + ":" + dependency.getName() + ":";
+			Pattern pattern = Pattern.compile("\"" + ga + "\\$\\{?([^'\"]+?)\\}?\"");
+			String buildFileText = readString(scanFile);
+			Matcher matcher = pattern.matcher(buildFileText);
+			while (matcher.find()) {
+				String versionVariable = matcher.group(1);
+				gradlePropertiesText = gradlePropertiesText.replace(versionVariable + "=" + dependency.getVersion(), versionVariable + "=" + updatedVersion(dependency));
+			}
+			return gradlePropertiesText;
+		});
+	}
+
+	private static String updatedVersion(DependencyOutdated dependency) {
+		VersionAvailable available = dependency.getAvailable();
+		String release = available.getRelease();
+		if (release != null) {
+			return release;
+		}
+		return available.getMilestone();
+	}
+}

Деякі файли не було показано, через те що забагато файлів було змінено