diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeRepository.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeRepository.java
new file mode 100644
index 00000000000..6ab0314d3a8
--- /dev/null
+++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeRepository.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright 2025 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.openrewrite.gradle;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+import org.jspecify.annotations.Nullable;
+import org.openrewrite.*;
+import org.openrewrite.gradle.internal.ChangeStringLiteral;
+import org.openrewrite.groovy.tree.G;
+import org.openrewrite.java.JavaIsoVisitor;
+import org.openrewrite.java.MethodMatcher;
+import org.openrewrite.java.RandomizeIdVisitor;
+import org.openrewrite.java.tree.J;
+import org.openrewrite.java.tree.JavaSourceFile;
+import org.openrewrite.java.tree.Statement;
+import org.openrewrite.kotlin.tree.K;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Objects;
+
+import static java.util.Collections.singletonList;
+import static java.util.stream.Collectors.toList;
+
+@Value
+@EqualsAndHashCode(callSuper = false)
+public class ChangeRepository extends Recipe {
+
+ String displayName = "Change repository";
+
+ String description = "Replace a repository in Gradle build scripts to standardize repository usage across an organization.";
+
+ @Option(displayName = "Old type",
+ description = "The type of the artifact repository to replace. " +
+ "Named types include \"jcenter\", \"mavenCentral\", \"mavenLocal\", \"google\", and \"gradlePluginPortal\". " +
+ "If not specified, matches any repository type with the given URL.",
+ required = false,
+ example = "jcenter")
+ @Nullable
+ String oldType;
+
+ @Option(displayName = "Old URL",
+ description = "The URL of the artifact repository to replace. If not specified, matches any repository of the given type.",
+ required = false,
+ example = "https://old-nexus.example.com/releases")
+ @Nullable
+ String oldUrl;
+
+ @Option(displayName = "New type",
+ description = "The type of the new artifact repository. " +
+ "If not specified, the matched repository's type will be preserved.",
+ required = false,
+ example = "mavenCentral")
+ @Nullable
+ String newType;
+
+ @Option(displayName = "New URL",
+ description = "The URL of the new artifact repository. Required when the new type is not a named repository.",
+ required = false,
+ example = "https://new-nexus.example.com/releases")
+ @Nullable
+ String newUrl;
+
+ @Override
+ public Validated validate() {
+ return super.validate()
+ .and(Validated.required("newType", newType).or(Validated.required("newUrl", newUrl)))
+ .and(Validated.test(
+ "repository",
+ "Old and new repository must be different",
+ this,
+ r -> !(Objects.equals(r.oldType, r.newType) && Objects.equals(r.oldUrl, r.newUrl))
+ ));
+ }
+
+ @Override
+ public TreeVisitor, ExecutionContext> getVisitor() {
+ MethodMatcher repoMatcher = new MethodMatcher("org.gradle.api.artifacts.dsl.RepositoryHandler " + (oldType != null ? oldType : "*") + "(..)", true);
+
+ return Preconditions.check(new IsBuildGradle<>(), new JavaIsoVisitor() {
+ @Override
+ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
+ J.MethodInvocation m = super.visitMethodInvocation(method, ctx);
+
+ if (!repoMatcher.matches(m, true)) {
+ return m;
+ }
+
+ // Ensure we're inside a repositories {} block
+ if (!isInsideRepositoriesBlock()) {
+ return m;
+ }
+
+ // If oldUrl is specified, verify the URL matches
+ if (oldUrl != null && !urlMatches(m)) {
+ return m;
+ }
+
+ // If newType is not specified, keep the matched repository's type
+ String effectiveNewType = newType != null ? newType : m.getSimpleName();
+
+ boolean isKotlinDsl = getCursor().firstEnclosingOrThrow(JavaSourceFile.class) instanceof K.CompilationUnit;
+
+ // If the target repository already exists as a sibling, remove the old one instead of replacing
+ if (newRepoAlreadyExists(m, effectiveNewType)) {
+ //noinspection DataFlowIssue
+ return null;
+ }
+
+ // Named → Named (no URLs involved)
+ if (oldUrl == null && newUrl == null) {
+ if (m.getSimpleName().equals(effectiveNewType)) {
+ return m;
+ }
+ return m.withName(m.getName().withSimpleName(effectiveNewType));
+ }
+
+ // Custom → Custom with same type: just change the URL
+ if (m.getSimpleName().equals(effectiveNewType) && newUrl != null) {
+ if (oldUrl != null && oldUrl.equals(newUrl)) {
+ return m;
+ }
+ return replaceUrl(m, newUrl, isKotlinDsl);
+ }
+
+ // All other cases: generate a new repository node and swap it in
+ J.MethodInvocation replacement = generateRepositoryInvocation(effectiveNewType, isKotlinDsl, ctx);
+ return (J.MethodInvocation) autoFormat(replacement.withPrefix(m.getPrefix()), ctx, getCursor().getParentOrThrow());
+ }
+
+ private boolean isInsideRepositoriesBlock() {
+ try {
+ getCursor().dropParentUntil(e ->
+ e instanceof J.MethodInvocation &&
+ "repositories".equals(((J.MethodInvocation) e).getSimpleName()));
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private boolean newRepoAlreadyExists(J.MethodInvocation current, String effectiveNewType) {
+ try {
+ Cursor reposCursor = getCursor().dropParentUntil(e ->
+ e instanceof J.MethodInvocation &&
+ "repositories".equals(((J.MethodInvocation) e).getSimpleName()));
+ J.MethodInvocation repos = reposCursor.getValue();
+ if (repos.getArguments().isEmpty() || !(repos.getArguments().get(0) instanceof J.Lambda)) {
+ return false;
+ }
+ J.Lambda lambda = (J.Lambda) repos.getArguments().get(0);
+ if (!(lambda.getBody() instanceof J.Block)) {
+ return false;
+ }
+ J.Block block = (J.Block) lambda.getBody();
+ for (Statement sibling : block.getStatements()) {
+ Statement s = sibling instanceof J.Return ? (Statement) ((J.Return) sibling).getExpression() : sibling;
+ if (!(s instanceof J.MethodInvocation)) {
+ continue;
+ }
+ J.MethodInvocation siblingInvocation = (J.MethodInvocation) s;
+ // Skip the node we're currently visiting
+ if (siblingInvocation.getId().equals(current.getId())) {
+ continue;
+ }
+ if (!effectiveNewType.equals(siblingInvocation.getSimpleName())) {
+ continue;
+ }
+ if (newUrl == null) {
+ // Named repo match (e.g. mavenCentral())
+ return true;
+ }
+ // Custom repo: check URL matches
+ String siblingUrl = extractUrlFromInvocation(siblingInvocation);
+ if (newUrl.equals(siblingUrl)) {
+ return true;
+ }
+ }
+ } catch (Exception ignored) {
+ }
+ return false;
+ }
+
+ private @Nullable String extractUrlFromInvocation(J.MethodInvocation m) {
+ if (m.getArguments().isEmpty() || !(m.getArguments().get(0) instanceof J.Lambda)) {
+ return null;
+ }
+ J.Lambda lambda = (J.Lambda) m.getArguments().get(0);
+ if (!(lambda.getBody() instanceof J.Block)) {
+ return null;
+ }
+ J.Block block = (J.Block) lambda.getBody();
+ for (Statement statement : block.getStatements()) {
+ Statement s = statement instanceof J.Return ? (Statement) ((J.Return) statement).getExpression() : statement;
+ if (s != null) {
+ String url = extractUrl(s);
+ if (url != null) {
+ return url;
+ }
+ }
+ }
+ return null;
+ }
+
+ private boolean urlMatches(J.MethodInvocation m) {
+ if (m.getArguments().isEmpty() || !(m.getArguments().get(0) instanceof J.Lambda)) {
+ return false;
+ }
+ J.Lambda lambda = (J.Lambda) m.getArguments().get(0);
+ if (!(lambda.getBody() instanceof J.Block)) {
+ return false;
+ }
+ J.Block block = (J.Block) lambda.getBody();
+ for (Statement statement : block.getStatements()) {
+ Statement s = statement instanceof J.Return ? (Statement) ((J.Return) statement).getExpression() : statement;
+ if (s == null) {
+ continue;
+ }
+ String extractedUrl = extractUrl(s);
+ if (oldUrl.equals(extractedUrl)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private @Nullable String extractUrl(Statement s) {
+ // Handle: url = "..." or url = uri("...")
+ if (s instanceof J.Assignment) {
+ J.Assignment assignment = (J.Assignment) s;
+ if (assignment.getVariable() instanceof J.Identifier &&
+ "url".equals(((J.Identifier) assignment.getVariable()).getSimpleName())) {
+ return extractUrlFromExpression(assignment.getAssignment());
+ }
+ }
+ // Handle: setUrl("...") or url("...") or setUrl(uri("..."))
+ if (s instanceof J.MethodInvocation) {
+ J.MethodInvocation mi = (J.MethodInvocation) s;
+ if ("setUrl".equals(mi.getSimpleName()) || "url".equals(mi.getSimpleName())) {
+ return extractUrlFromExpression(mi.getArguments().get(0));
+ }
+ }
+ return null;
+ }
+
+ private @Nullable String extractUrlFromExpression(J expr) {
+ if (expr instanceof J.Literal) {
+ return (String) ((J.Literal) expr).getValue();
+ }
+ if (expr instanceof J.MethodInvocation && "uri".equals(((J.MethodInvocation) expr).getSimpleName())) {
+ J.MethodInvocation uri = (J.MethodInvocation) expr;
+ if (!uri.getArguments().isEmpty() && uri.getArguments().get(0) instanceof J.Literal) {
+ return (String) ((J.Literal) uri.getArguments().get(0)).getValue();
+ }
+ }
+ return null;
+ }
+
+ private J.MethodInvocation replaceUrl(J.MethodInvocation m, String newUrl, boolean isKotlinDsl) {
+ return (J.MethodInvocation) new JavaIsoVisitor() {
+ @Override
+ public J.Literal visitLiteral(J.Literal literal, ExecutionContext ctx) {
+ if (literal.getValue() instanceof String && literal.getValue().equals(oldUrl)) {
+ return ChangeStringLiteral.withStringValue(literal, newUrl);
+ }
+ return literal;
+ }
+ }.visitNonNull(m, new InMemoryExecutionContext());
+ }
+
+ private J.MethodInvocation generateRepositoryInvocation(String effectiveNewType, boolean isKotlinDsl, ExecutionContext ctx) {
+ String code;
+ if (newUrl == null) {
+ code = effectiveNewType + "()";
+ } else if (isKotlinDsl) {
+ code = effectiveNewType + " {\n url = uri(\"" + newUrl + "\")\n}";
+ } else {
+ code = effectiveNewType + " {\n url = \"" + newUrl + "\"\n}";
+ }
+
+ String template = "repositories {\n " + code + "\n}";
+ Path path = Paths.get(isKotlinDsl ? "build.gradle.kts" : "build.gradle");
+
+ J.MethodInvocation reposBlock;
+ if (isKotlinDsl) {
+ K.CompilationUnit cu = GradleParser.builder().build()
+ .parseInputs(singletonList(Parser.Input.fromString(path, template)), null, ctx)
+ .map(K.CompilationUnit.class::cast)
+ .collect(toList()).get(0);
+ J.Block block = (J.Block) cu.getStatements().get(0);
+ reposBlock = (J.MethodInvocation) block.getStatements().get(0);
+ } else {
+ G.CompilationUnit cu = GradleParser.builder().build()
+ .parseInputs(singletonList(Parser.Input.fromString(path, template)), null, ctx)
+ .map(G.CompilationUnit.class::cast)
+ .collect(toList()).get(0);
+ reposBlock = (J.MethodInvocation) cu.getStatements().get(0);
+ }
+
+ // Extract the repository from inside the repositories {} block
+ J.Lambda lambda = (J.Lambda) reposBlock.getArguments().get(0);
+ J.Block body = (J.Block) lambda.getBody();
+ Statement stmt = body.getStatements().get(0);
+ J.MethodInvocation repo;
+ if (stmt instanceof J.Return) {
+ repo = (J.MethodInvocation) ((J.Return) stmt).getExpression();
+ } else {
+ repo = (J.MethodInvocation) stmt;
+ }
+
+ return (J.MethodInvocation) new RandomizeIdVisitor().visit(repo, 0);
+ }
+ });
+ }
+}
diff --git a/rewrite-gradle/src/main/resources/META-INF/rewrite/recipes.csv b/rewrite-gradle/src/main/resources/META-INF/rewrite/recipes.csv
index 9008b3883f6..801524c7188 100644
--- a/rewrite-gradle/src/main/resources/META-INF/rewrite/recipes.csv
+++ b/rewrite-gradle/src/main/resources/META-INF/rewrite/recipes.csv
@@ -7,6 +7,7 @@ maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.ChangeDependencyGrou
maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.ChangeExtraProperty,Change Extra Property,Gradle's [ExtraPropertiesExtension](https://docs.gradle.org/current/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.html) is a commonly used mechanism for setting arbitrary key/value pairs on a project. This recipe will change the value of a property with the given key name if that key can be found. It assumes that the value being set is a String literal. Does not add the value if it does not already exist.,1,,Gradle,"[{""name"":""key"",""type"":""String"",""displayName"":""Key"",""description"":""The key of the property to change."",""example"":""foo"",""required"":true},{""name"":""value"",""type"":""String"",""displayName"":""Value"",""description"":""The new value to set. The value will be treated the contents of a string literal."",""example"":""bar"",""required"":true}]",
maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.ChangeManagedDependency,Change Gradle managed dependency,"Change a Gradle managed dependency coordinates. The `newGroupId` or `newArtifactId` **MUST** be different from before.
For now, only Spring Dependency Management Plugin entries are supported and no other forms of managed dependencies (yet).",1,,Gradle,"[{""name"":""oldGroupId"",""type"":""String"",""displayName"":""Old groupId"",""description"":""The old groupId to replace. The groupId is the first part of a dependency coordinate 'com.google.guava:guava:VERSION'. Supports glob expressions."",""example"":""org.openrewrite.recipe"",""required"":true},{""name"":""oldArtifactId"",""type"":""String"",""displayName"":""Old artifactId"",""description"":""The old artifactId to replace. The artifactId is the second part of a dependency coordinate 'com.google.guava:guava:VERSION'. Supports glob expressions."",""example"":""rewrite-testing-frameworks"",""required"":true},{""name"":""newGroupId"",""type"":""String"",""displayName"":""New groupId"",""description"":""The new groupId to use. Defaults to the existing group id."",""example"":""corp.internal.openrewrite.recipe""},{""name"":""newArtifactId"",""type"":""String"",""displayName"":""New artifactId"",""description"":""The new artifactId to use. Defaults to the existing artifact id."",""example"":""rewrite-testing-frameworks""},{""name"":""newVersion"",""type"":""String"",""displayName"":""New version"",""description"":""An exact version number or node-style semver selector used to select the version number. You can also use `latest.release` for the latest available version and `latest.patch` if the current version is a valid semantic version. For more details, you can look at the documentation page of [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors)."",""example"":""29.X""},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example,Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""}]","[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.ChangeRepository,Change repository,Replace a repository in Gradle build scripts to standardize repository usage across an organization.,1,,Gradle,"[{""name"":""oldType"",""type"":""String"",""displayName"":""Old type"",""description"":""The type of the artifact repository to replace. Named types include \""jcenter\"", \""mavenCentral\"", \""mavenLocal\"", \""google\"", and \""gradlePluginPortal\"". If not specified, matches any repository type with the given URL."",""example"":""jcenter""},{""name"":""oldUrl"",""type"":""String"",""displayName"":""Old URL"",""description"":""The URL of the artifact repository to replace. If not specified, matches any repository of the given type."",""example"":""https://old-nexus.example.com/releases""},{""name"":""newType"",""type"":""String"",""displayName"":""New type"",""description"":""The type of the new artifact repository. If not specified, the matched repository's type will be preserved."",""example"":""mavenCentral""},{""name"":""newUrl"",""type"":""String"",""displayName"":""New URL"",""description"":""The URL of the new artifact repository. Required when the new type is not a named repository."",""example"":""https://new-nexus.example.com/releases""}]",
maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.ChangeTaskToTasksRegister,Change Gradle task eager creation to lazy registration,"Changes eager task creation `task exampleName(type: ExampleType)` to lazy registration `tasks.register(""exampleName"", ExampleType)`. Also supports Kotlin DSL: `task(""exampleName"")` to `tasks.register(""exampleName"")`.",1,,Gradle,,
maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.DependencyConstraintToRule,Dependency constraint to resolution rule,"Gradle [dependency constraints](https://docs.gradle.org/current/userguide/dependency_constraints.html#dependency-constraints) are useful for managing the versions of transitive dependencies. Some plugins, such as the Spring Dependency Management plugin, do not respect these constraints. This recipe converts constraints into [resolution rules](https://docs.gradle.org/current/userguide/resolution_rules.html), which can achieve similar effects to constraints but are harder for plugins to ignore.",1,,Gradle,,
maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.DependencyUseMapNotation,Use `Map` notation for Gradle dependency declarations,"In Gradle, dependencies can be expressed as a `String` like `""groupId:artifactId:version""`, or equivalently as a `Map` like `group: 'groupId', name: 'artifactId', version: 'version'` (groovy) or `group = ""groupId"", name = ""artifactId"", version = ""version""` (kotlin). This recipe replaces dependencies represented as `Strings` with an equivalent dependency represented as a `Map`.",1,,Gradle,,
diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeRepositoryTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeRepositoryTest.java
new file mode 100644
index 00000000000..e61140ee7b7
--- /dev/null
+++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeRepositoryTest.java
@@ -0,0 +1,565 @@
+/*
+ * Copyright 2025 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.openrewrite.gradle;
+
+import org.junit.jupiter.api.Test;
+import org.openrewrite.DocumentExample;
+import org.openrewrite.test.RewriteTest;
+
+import static org.openrewrite.gradle.Assertions.buildGradle;
+import static org.openrewrite.gradle.Assertions.buildGradleKts;
+
+class ChangeRepositoryTest implements RewriteTest {
+
+ @DocumentExample
+ @Test
+ void namedToNamed() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("jcenter", null, "mavenCentral", null)),
+ buildGradle(
+ """
+ repositories {
+ jcenter()
+ }
+ """,
+ """
+ repositories {
+ mavenCentral()
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void namedToNamedKts() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("jcenter", null, "mavenCentral", null)),
+ buildGradleKts(
+ """
+ repositories {
+ jcenter()
+ }
+ """,
+ """
+ repositories {
+ mavenCentral()
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void namedToCustomMaven() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("jcenter", null, "maven", "https://nexus.example.com/releases")),
+ buildGradle(
+ """
+ repositories {
+ jcenter()
+ }
+ """,
+ """
+ repositories {
+ maven {
+ url = "https://nexus.example.com/releases"
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void namedToCustomMavenKts() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("jcenter", null, "maven", "https://nexus.example.com/releases")),
+ buildGradleKts(
+ """
+ repositories {
+ jcenter()
+ }
+ """,
+ """
+ repositories {
+ maven {
+ url = uri("https://nexus.example.com/releases")
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void customMavenToNamed() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("maven", "https://jcenter.bintray.com", "mavenCentral", null)),
+ buildGradle(
+ """
+ repositories {
+ maven {
+ url = "https://jcenter.bintray.com"
+ }
+ }
+ """,
+ """
+ repositories {
+ mavenCentral()
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void customMavenToNamedKts() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("maven", "https://jcenter.bintray.com", "mavenCentral", null)),
+ buildGradleKts(
+ """
+ repositories {
+ maven {
+ url = uri("https://jcenter.bintray.com")
+ }
+ }
+ """,
+ """
+ repositories {
+ mavenCentral()
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void changeUrl() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")),
+ buildGradle(
+ """
+ repositories {
+ maven {
+ url = "https://old-nexus.example.com/releases"
+ }
+ }
+ """,
+ """
+ repositories {
+ maven {
+ url = "https://new-nexus.example.com/releases"
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void changeUrlKts() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")),
+ buildGradleKts(
+ """
+ repositories {
+ maven {
+ url = uri("https://old-nexus.example.com/releases")
+ }
+ }
+ """,
+ """
+ repositories {
+ maven {
+ url = uri("https://new-nexus.example.com/releases")
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void noChangeWhenAlreadyCorrect() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("jcenter", null, "mavenCentral", null)),
+ buildGradle(
+ """
+ repositories {
+ mavenCentral()
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void onlyMatchesSpecifiedUrl() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")),
+ buildGradle(
+ """
+ repositories {
+ maven {
+ url = "https://other-repo.example.com/releases"
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void preservesOtherRepositories() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("jcenter", null, "mavenCentral", null)),
+ buildGradle(
+ """
+ repositories {
+ jcenter()
+ mavenLocal()
+ }
+ """,
+ """
+ repositories {
+ mavenCentral()
+ mavenLocal()
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void changeUrlUsingSetUrlMethod() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")),
+ buildGradle(
+ """
+ repositories {
+ maven {
+ setUrl("https://old-nexus.example.com/releases")
+ }
+ }
+ """,
+ """
+ repositories {
+ maven {
+ setUrl("https://new-nexus.example.com/releases")
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void doesNotMatchOutsideRepositoriesBlock() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("jcenter", null, "mavenCentral", null)),
+ buildGradle(
+ """
+ repositories {
+ mavenLocal()
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void matchByUrlOnly() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository(null, "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")),
+ buildGradle(
+ """
+ repositories {
+ maven {
+ url = "https://old-nexus.example.com/releases"
+ }
+ }
+ """,
+ """
+ repositories {
+ maven {
+ url = "https://new-nexus.example.com/releases"
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void newUrlOnlyKeepsType() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", null, "https://new-nexus.example.com/releases")),
+ buildGradle(
+ """
+ repositories {
+ maven {
+ url = "https://old-nexus.example.com/releases"
+ }
+ }
+ """,
+ """
+ repositories {
+ maven {
+ url = "https://new-nexus.example.com/releases"
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void newUrlOnlyKeepsTypeKts() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository(null, "https://old-nexus.example.com/releases", null, "https://new-nexus.example.com/releases")),
+ buildGradleKts(
+ """
+ repositories {
+ maven {
+ url = uri("https://old-nexus.example.com/releases")
+ }
+ }
+ """,
+ """
+ repositories {
+ maven {
+ url = uri("https://new-nexus.example.com/releases")
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void matchByUrlOnlyReplace() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository(null, "https://old-nexus.example.com/releases", "mavenCentral", null)),
+ buildGradle(
+ """
+ repositories {
+ maven {
+ url = "https://old-nexus.example.com/releases"
+ }
+ }
+ """,
+ """
+ repositories {
+ mavenCentral()
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void preservesCredentialsWhenChangingUrl() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")),
+ buildGradle(
+ """
+ repositories {
+ maven {
+ url = "https://old-nexus.example.com/releases"
+ credentials {
+ username = findProperty("mavenUsername")
+ password = findProperty("mavenPassword")
+ }
+ }
+ }
+ """,
+ """
+ repositories {
+ maven {
+ url = "https://new-nexus.example.com/releases"
+ credentials {
+ username = findProperty("mavenUsername")
+ password = findProperty("mavenPassword")
+ }
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void preservesCredentialsWhenChangingUrlKts() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")),
+ buildGradleKts(
+ """
+ repositories {
+ maven {
+ url = uri("https://old-nexus.example.com/releases")
+ credentials {
+ username = System.getenv("MAVEN_USERNAME")
+ password = System.getenv("MAVEN_PASSWORD")
+ }
+ }
+ }
+ """,
+ """
+ repositories {
+ maven {
+ url = uri("https://new-nexus.example.com/releases")
+ credentials {
+ username = System.getenv("MAVEN_USERNAME")
+ password = System.getenv("MAVEN_PASSWORD")
+ }
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void preservesAuthenticationBlockWhenChangingUrl() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")),
+ buildGradle(
+ """
+ repositories {
+ maven {
+ url = "https://old-nexus.example.com/releases"
+ credentials {
+ username = findProperty("mavenUsername")
+ password = findProperty("mavenPassword")
+ }
+ authentication {
+ basic(BasicAuthentication)
+ }
+ }
+ }
+ """,
+ """
+ repositories {
+ maven {
+ url = "https://new-nexus.example.com/releases"
+ credentials {
+ username = findProperty("mavenUsername")
+ password = findProperty("mavenPassword")
+ }
+ authentication {
+ basic(BasicAuthentication)
+ }
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void preservesNameAndOtherPropertiesWhenChangingUrl() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")),
+ buildGradle(
+ """
+ repositories {
+ maven {
+ name = "myRepo"
+ url = "https://old-nexus.example.com/releases"
+ allowInsecureProtocol = true
+ }
+ }
+ """,
+ """
+ repositories {
+ maven {
+ name = "myRepo"
+ url = "https://new-nexus.example.com/releases"
+ allowInsecureProtocol = true
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void removesOldWhenTargetNamedRepoAlreadyExists() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("jcenter", null, "mavenCentral", null)),
+ buildGradle(
+ """
+ repositories {
+ jcenter()
+ mavenCentral()
+ }
+ """,
+ """
+ repositories {
+ mavenCentral()
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void removesOldWhenTargetCustomRepoAlreadyExists() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")),
+ buildGradle(
+ """
+ repositories {
+ maven {
+ url = "https://old-nexus.example.com/releases"
+ }
+ maven {
+ url = "https://new-nexus.example.com/releases"
+ }
+ }
+ """,
+ """
+ repositories {
+ maven {
+ url = "https://new-nexus.example.com/releases"
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void removesOldWhenTargetNamedRepoAlreadyExistsKts() {
+ rewriteRun(
+ spec -> spec.recipe(new ChangeRepository("jcenter", null, "mavenCentral", null)),
+ buildGradleKts(
+ """
+ repositories {
+ jcenter()
+ mavenCentral()
+ }
+ """,
+ """
+ repositories {
+ mavenCentral()
+ }
+ """
+ )
+ );
+ }
+}