diff --git a/pom.xml b/pom.xml
index 63517947..079d68f3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -295,6 +295,11 @@ under the License.
commons-io
2.20.0
+
+ org.apache.maven.scm
+ maven-scm-api
+ 2.1.0
+
diff --git a/src/main/java/org/apache/maven/plugins/site/deploy/AbstractDeployMojo.java b/src/main/java/org/apache/maven/plugins/site/deploy/AbstractDeployMojo.java
index f280cdd0..daeaa040 100644
--- a/src/main/java/org/apache/maven/plugins/site/deploy/AbstractDeployMojo.java
+++ b/src/main/java/org/apache/maven/plugins/site/deploy/AbstractDeployMojo.java
@@ -37,6 +37,7 @@
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.site.AbstractSiteMojo;
import org.apache.maven.project.MavenProject;
+import org.apache.maven.scm.provider.ScmUrlUtils;
import org.apache.maven.settings.Proxy;
import org.apache.maven.settings.Server;
import org.apache.maven.settings.Settings;
@@ -492,6 +493,66 @@ private static String getFullName(MavenProject project) {
+ project.getVersion() + ')';
}
+ /**
+ * Extracts the provider-specific URL from an SCM URL for comparison purposes.
+ * For non-SCM URLs, returns the original URL.
+ * For SCM URLs with SCP-like syntax (e.g., git@github.com:user/repo.git),
+ * converts them to a comparable format.
+ * For hierarchical SCM systems like SVN, normalizes the scheme to enable
+ * proper comparison of URLs that differ only in http vs https.
+ *
+ * @param url the URL to process
+ * @return the provider-specific URL for SCM URLs, or the original URL otherwise
+ */
+ static String extractComparableUrl(String url) {
+ if (url != null && url.startsWith("scm:")) {
+ // Extract the SCM provider (e.g., "git", "svn")
+ String provider = ScmUrlUtils.getProvider(url);
+
+ // Extract the provider-specific part of the SCM URL
+ // For example: "scm:git:https://github.com/user/repo.git" -> "https://github.com/user/repo.git"
+ String providerSpecificPart = ScmUrlUtils.getProviderSpecificPart(url);
+ if (providerSpecificPart != null && !providerSpecificPart.isEmpty()) {
+ // Handle SCP-like Git syntax (e.g., git@github.com:user/repo.git or user@host:path)
+ // Convert it to a more standard format for comparison
+ // Note: This is a heuristic check - we look for the pattern of user@host:path
+ // where the colon comes after the @ symbol and is followed by a path
+ if (providerSpecificPart.contains("@")
+ && !providerSpecificPart.startsWith("http://")
+ && !providerSpecificPart.startsWith("https://")
+ && !providerSpecificPart.startsWith("ssh://")) {
+ // Find the @ symbol and look for the first : after it that's not part of a URL scheme
+ int atIndex = providerSpecificPart.lastIndexOf('@');
+ int colonIndex = providerSpecificPart.indexOf(':', atIndex);
+
+ // Verify this looks like SCP syntax: user@host:path
+ // The colon should come after @ and before the end
+ if (atIndex >= 0 && colonIndex > atIndex + 1 && colonIndex < providerSpecificPart.length() - 1) {
+ String host = providerSpecificPart.substring(atIndex + 1, colonIndex);
+ String path = providerSpecificPart.substring(colonIndex + 1);
+ // Convert to a pseudo-URL format for comparison
+ // Note: IPv6 addresses in brackets are handled by this approach
+ // as the brackets will be preserved in the host part
+ return "ssh://" + host + "/" + path;
+ }
+ }
+
+ // For hierarchical VCS systems like SVN, normalize the scheme to allow
+ // comparison of URLs that differ only in http vs https
+ // SVN repositories can be accessed via both protocols and should be considered the same
+ if ("svn".equalsIgnoreCase(provider) && providerSpecificPart.startsWith("https://")) {
+ // Normalize https to http for SVN URLs to enable proper comparison
+ return "http" + providerSpecificPart.substring(5);
+ }
+
+ // Return the provider-specific part as-is for standard URLs or
+ // if SCP syntax conversion is not applicable
+ return providerSpecificPart;
+ }
+ }
+ return url;
+ }
+
/**
* Extract the distributionManagement site of the top level parent of the given MavenProject.
* This climbs up the project hierarchy and returns the site of the last project
@@ -523,8 +584,12 @@ protected MavenProject getTopLevelProject(MavenProject project) throws MojoExecu
}
// MSITE-600
- URIPathDescriptor siteURI = new URIPathDescriptor(URIEncoder.encodeURI(site.getUrl()), "");
- URIPathDescriptor oldSiteURI = new URIPathDescriptor(URIEncoder.encodeURI(oldSite.getUrl()), "");
+ // MSITE-1033: For SCM URLs, extract the provider-specific part for comparison
+ String siteUrlToCompare = extractComparableUrl(site.getUrl());
+ String oldSiteUrlToCompare = extractComparableUrl(oldSite.getUrl());
+
+ URIPathDescriptor siteURI = new URIPathDescriptor(URIEncoder.encodeURI(siteUrlToCompare), "");
+ URIPathDescriptor oldSiteURI = new URIPathDescriptor(URIEncoder.encodeURI(oldSiteUrlToCompare), "");
if (!siteURI.sameSite(oldSiteURI.getBaseURI())) {
return oldProject;
diff --git a/src/test/java/org/apache/maven/plugins/site/deploy/AbstractDeployMojoTest.java b/src/test/java/org/apache/maven/plugins/site/deploy/AbstractDeployMojoTest.java
new file mode 100644
index 00000000..bd7a0540
--- /dev/null
+++ b/src/test/java/org/apache/maven/plugins/site/deploy/AbstractDeployMojoTest.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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.apache.maven.plugins.site.deploy;
+
+import org.apache.maven.model.DistributionManagement;
+import org.apache.maven.model.Site;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.project.MavenProject;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests for AbstractDeployMojo.
+ */
+public class AbstractDeployMojoTest {
+
+ /**
+ * Test that getTopLevelProject correctly handles SCM URLs with different repositories.
+ * This is the test case for MSITE-1033.
+ */
+ @Test
+ public void testGetTopLevelProjectWithDifferentScmUrls() throws Exception {
+ // Create a mock deploy mojo
+ TestDeployMojo mojo = new TestDeployMojo();
+
+ // Create child project with SCM URL
+ MavenProject childProject =
+ createProjectWithSite("child", "scm:git:git@github.com:codehaus-plexus/plexus-sec-dispatcher.git/");
+
+ // Create parent project with different SCM URL
+ MavenProject parentProject =
+ createProjectWithSite("parent", "scm:git:https://github.com/codehaus-plexus/plexus-pom.git/");
+
+ // Set up the parent-child relationship
+ childProject.setParent(parentProject);
+
+ // Call getTopLevelProject - it should return childProject, not parentProject
+ // because the SCM URLs point to different repositories
+ MavenProject topProject = mojo.getTopLevelProject(childProject);
+
+ // The top project should be the child project itself since the parent has a different site
+ assertEquals("Top project should be child project due to different SCM URLs", childProject, topProject);
+ }
+
+ /**
+ * Test that getTopLevelProject correctly handles SCM URLs with the same repository.
+ */
+ @Test
+ public void testGetTopLevelProjectWithSameScmUrls() throws Exception {
+ // Create a mock deploy mojo
+ TestDeployMojo mojo = new TestDeployMojo();
+
+ // Create child project with SCM URL
+ MavenProject childProject =
+ createProjectWithSite("child", "scm:git:https://github.com/codehaus-plexus/plexus-pom.git/child");
+
+ // Create parent project with same base SCM URL
+ MavenProject parentProject =
+ createProjectWithSite("parent", "scm:git:https://github.com/codehaus-plexus/plexus-pom.git/");
+
+ // Set up the parent-child relationship
+ childProject.setParent(parentProject);
+
+ // Call getTopLevelProject - it should return parentProject
+ // because the SCM URLs point to the same repository
+ MavenProject topProject = mojo.getTopLevelProject(childProject);
+
+ // The top project should be the parent project since they share the same site
+ assertEquals("Top project should be parent project due to same SCM base URL", parentProject, topProject);
+ }
+
+ /**
+ * Test that getTopLevelProject correctly handles non-SCM URLs.
+ */
+ @Test
+ public void testGetTopLevelProjectWithNonScmUrls() throws Exception {
+ // Create a mock deploy mojo
+ TestDeployMojo mojo = new TestDeployMojo();
+
+ // Create child project with regular URL
+ MavenProject childProject = createProjectWithSite("child", "https://example.com/site/child");
+
+ // Create parent project with same base URL
+ MavenProject parentProject = createProjectWithSite("parent", "https://example.com/site/");
+
+ // Set up the parent-child relationship
+ childProject.setParent(parentProject);
+
+ // Call getTopLevelProject - it should return parentProject
+ MavenProject topProject = mojo.getTopLevelProject(childProject);
+
+ // The top project should be the parent project since they share the same site
+ assertEquals("Top project should be parent project for regular URLs", parentProject, topProject);
+ }
+
+ /**
+ * Test that getTopLevelProject correctly handles SCM URLs with standard https format.
+ */
+ @Test
+ public void testGetTopLevelProjectWithHttpsScmUrls() throws Exception {
+ // Create a mock deploy mojo
+ TestDeployMojo mojo = new TestDeployMojo();
+
+ // Create child project with https SCM URL
+ MavenProject childProject = createProjectWithSite("child", "scm:git:https://github.com/user/repo1.git/");
+
+ // Create parent project with different https SCM URL (different domain)
+ MavenProject parentProject = createProjectWithSite("parent", "scm:git:https://gitlab.com/user/repo2.git/");
+
+ // Set up the parent-child relationship
+ childProject.setParent(parentProject);
+
+ // Call getTopLevelProject - it should return childProject
+ // because the SCM URLs point to different repositories
+ MavenProject topProject = mojo.getTopLevelProject(childProject);
+
+ // The top project should be the child project itself since the parent has a different site
+ assertEquals("Top project should be child project due to different https SCM URLs", childProject, topProject);
+ }
+
+ /**
+ * Test that extractComparableUrl properly handles SVN URLs with different schemes but same host.
+ * For SVN (hierarchical VCS), URLs with different schemes (http vs https) should be normalized
+ * to the same scheme to allow URIPathDescriptor.sameSite() to recognize them as the same site.
+ * Note: The paths may differ (one being a subpath of another), but as long as scheme, host, and port
+ * are the same, URIPathDescriptor.sameSite() will correctly identify them as the same site.
+ */
+ @Test
+ public void testExtractComparableUrlForSvnUrls() throws Exception {
+ // Extract and normalize both URLs
+ String url1 = AbstractDeployMojo.extractComparableUrl("scm:svn:http://svn.apache.org/repos/asf/maven/website");
+ String url2 = AbstractDeployMojo.extractComparableUrl(
+ "scm:svn:https://svn.apache.org/repos/asf/maven/website/components");
+
+ // Both should be normalized to http scheme
+ assertEquals("http://svn.apache.org/repos/asf/maven/website", url1);
+ assertEquals("http://svn.apache.org/repos/asf/maven/website/components", url2);
+
+ // Now verify they would be considered the same site by creating projects with these URLs
+ TestDeployMojo mojo = new TestDeployMojo();
+ MavenProject childProject =
+ createProjectWithSite("child", "scm:svn:https://svn.apache.org/repos/asf/maven/website/components");
+ MavenProject parentProject =
+ createProjectWithSite("parent", "scm:svn:http://svn.apache.org/repos/asf/maven/website");
+ childProject.setParent(parentProject);
+
+ // The parent should be returned as the top project since they share the same SVN site
+ MavenProject topProject = mojo.getTopLevelProject(childProject);
+ assertEquals(
+ "Top project should be parent due to normalized SVN URLs pointing to same site",
+ parentProject,
+ topProject);
+ }
+
+ private MavenProject createProjectWithSite(String artifactId, String siteUrl) {
+ MavenProject project = new MavenProject();
+ project.setGroupId("org.apache.maven.test");
+ project.setArtifactId(artifactId);
+ project.setVersion("1.0");
+ project.setName(artifactId);
+
+ DistributionManagement distributionManagement = new DistributionManagement();
+ Site site = new Site();
+ site.setId("test-site");
+ site.setUrl(siteUrl);
+ distributionManagement.setSite(site);
+ project.setDistributionManagement(distributionManagement);
+
+ return project;
+ }
+
+ /**
+ * Test implementation of AbstractDeployMojo for testing.
+ */
+ private static class TestDeployMojo extends AbstractDeployMojo {
+ @Override
+ protected boolean isDeploy() {
+ return true;
+ }
+
+ @Override
+ protected String determineTopDistributionManagementSiteUrl() throws MojoExecutionException {
+ return "test";
+ }
+
+ @Override
+ protected Site determineDeploySite() throws MojoExecutionException {
+ Site site = new Site();
+ site.setId("test");
+ site.setUrl("test");
+ return site;
+ }
+ }
+}