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; + } + } +}