diff --git a/pom.xml b/pom.xml index 911959bb9..079d68f34 100644 --- a/pom.xml +++ b/pom.xml @@ -214,7 +214,8 @@ under the License. 4.0.0 2.0.0-M11 2024-07-17T13:43:56Z - + + 3.9.1 @@ -294,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/it/projects/effective-site/pom.xml b/src/it/projects/effective-site/pom.xml index d7898fe1f..86083dbc3 100644 --- a/src/it/projects/effective-site/pom.xml +++ b/src/it/projects/effective-site/pom.xml @@ -27,7 +27,7 @@ under the License. org.apache.maven.plugins maven-plugins - 43 + 45 org.apache.maven.plugins.site.its diff --git a/src/it/projects/full-reporting/pom.xml b/src/it/projects/full-reporting/pom.xml index 30f726f94..2b6458880 100644 --- a/src/it/projects/full-reporting/pom.xml +++ b/src/it/projects/full-reporting/pom.xml @@ -22,7 +22,7 @@ under the License. org.apache.maven maven-parent - 43 + 45 org.apache.maven.plugins.site.its diff --git a/src/it/projects/full-reporting/verify.groovy b/src/it/projects/full-reporting/verify.groovy index deaa154e6..023b4c8db 100644 --- a/src/it/projects/full-reporting/verify.groovy +++ b/src/it/projects/full-reporting/verify.groovy @@ -27,7 +27,7 @@ assert content.contains( 'Tests run: 1, Failures: 0, Errors: 0, Skipped: 0' ); sitedir = new File( basedir, 'target/site' ); -assert new File( sitedir, 'surefire-report.html' ).exists(); +assert new File( sitedir, 'surefire.html' ).exists(); assert new File( sitedir, 'index.html' ).exists(); assert new File( sitedir, 'checkstyle.html' ).exists(); assert new File( sitedir, 'cpd.html' ).exists(); diff --git a/src/main/java/org/apache/maven/plugins/site/AbstractSiteMojo.java b/src/main/java/org/apache/maven/plugins/site/AbstractSiteMojo.java index 44fe1adbf..75f6e9f3f 100644 --- a/src/main/java/org/apache/maven/plugins/site/AbstractSiteMojo.java +++ b/src/main/java/org/apache/maven/plugins/site/AbstractSiteMojo.java @@ -18,12 +18,13 @@ */ package org.apache.maven.plugins.site; +import javax.inject.Inject; + import java.util.List; import java.util.Locale; import org.apache.maven.doxia.tools.SiteTool; import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.i18n.I18N; @@ -54,13 +55,13 @@ public abstract class AbstractSiteMojo extends AbstractMojo { /** * SiteTool. */ - @Component + @Inject protected SiteTool siteTool; /** * Internationalization. */ - @Component + @Inject protected I18N i18n; /** 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 204f2fc61..daeaa040e 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 @@ -18,11 +18,14 @@ */ package org.apache.maven.plugins.site.deploy; +import javax.inject.Inject; + import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.List; import java.util.Locale; +import java.util.Map; import org.apache.maven.doxia.site.inheritance.URIPathDescriptor; import org.apache.maven.doxia.tools.SiteTool; @@ -31,10 +34,10 @@ import org.apache.maven.model.DistributionManagement; import org.apache.maven.model.Site; import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugins.annotations.Component; 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; @@ -53,8 +56,6 @@ import org.apache.maven.wagon.observers.Debug; import org.apache.maven.wagon.proxy.ProxyInfo; import org.apache.maven.wagon.repository.Repository; -import org.codehaus.plexus.PlexusContainer; -import org.codehaus.plexus.component.repository.exception.ComponentLookupException; /** * Abstract base class for deploy mojos. @@ -123,10 +124,10 @@ public abstract class AbstractDeployMojo extends AbstractSiteMojo { private Site deploySite; - @Component - private PlexusContainer container; + @Inject + private Map wagons; - @Component + @Inject SettingsDecrypter settingsDecrypter; /** @@ -277,16 +278,15 @@ private Wagon getWagon(final Repository repository) throws MojoExecutionExceptio if (protocol == null) { throw new MojoExecutionException("Unspecified protocol"); } - try { - Wagon wagon = container.lookup(Wagon.class, protocol.toLowerCase(Locale.ROOT)); - if (!wagon.supportsDirectoryCopy()) { - throw new MojoExecutionException( - "Wagon protocol '" + repository.getProtocol() + "' doesn't support directory copying"); - } - return wagon; - } catch (ComponentLookupException e) { - throw new MojoExecutionException("Cannot find wagon which supports the requested protocol: " + protocol, e); + Wagon wagon = wagons.get(protocol.toLowerCase(Locale.ROOT)); + if (wagon == null) { + throw new MojoExecutionException("Cannot find wagon which supports the requested protocol: " + protocol); + } + if (!wagon.supportsDirectoryCopy()) { + throw new MojoExecutionException( + "Wagon protocol '" + repository.getProtocol() + "' doesn't support directory copying"); } + return wagon; } public AuthenticationInfo getAuthenticationInfo(String id) { @@ -493,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 @@ -524,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/main/java/org/apache/maven/plugins/site/render/AbstractSiteRenderingMojo.java b/src/main/java/org/apache/maven/plugins/site/render/AbstractSiteRenderingMojo.java index 9628c6bb9..72270446d 100644 --- a/src/main/java/org/apache/maven/plugins/site/render/AbstractSiteRenderingMojo.java +++ b/src/main/java/org/apache/maven/plugins/site/render/AbstractSiteRenderingMojo.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.IOException; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -57,7 +58,6 @@ import org.apache.maven.reporting.exec.MavenReportExecution; import org.apache.maven.reporting.exec.MavenReportExecutor; import org.apache.maven.reporting.exec.MavenReportExecutorRequest; -import org.codehaus.plexus.util.ReaderFactory; import static org.apache.maven.shared.utils.logging.MessageUtils.buffer; @@ -192,7 +192,9 @@ protected AbstractSiteRenderingMojo( * @return The input files encoding, never null. */ protected String getInputEncoding() { - return (inputEncoding == null || inputEncoding.isEmpty()) ? ReaderFactory.FILE_ENCODING : inputEncoding; + return (inputEncoding == null || inputEncoding.isEmpty()) + ? Charset.defaultCharset().displayName() + : inputEncoding; } /** @@ -215,8 +217,8 @@ protected String getOutputEncoding() { protected void checkInputEncoding() { if (inputEncoding == null || inputEncoding.isEmpty()) { - getLog().warn("Input file encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING - + ", i.e. build is platform dependent!"); + getLog().warn("Input file encoding has not been set, using platform encoding " + + Charset.defaultCharset().displayName() + ", i.e. build is platform dependent!"); } } 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 000000000..bd7a0540d --- /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; + } + } +}