Copilot commented on code in PR #1297:
URL:
https://github.com/apache/maven-site-plugin/pull/1297#discussion_r3810560267
##########
src/main/java/org/apache/maven/plugins/site/deploy/AbstractDeployMojo.java:
##########
@@ -519,6 +522,101 @@ 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., [email protected]: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);
Review Comment:
`ScmUrlUtils.getProvider(..)` / `getProviderSpecificPart(..)` can throw
`IllegalArgumentException` for malformed `scm:` URLs. In that case
`getTopLevelProject` would fail the build instead of treating the URL as a
distinct site (as described in the PR rationale). Consider catching and falling
back to the original URL so a malformed SCM URL doesn’t crash site deployment.
##########
src/main/java/org/apache/maven/plugins/site/deploy/AbstractDeployMojo.java:
##########
@@ -519,6 +522,101 @@ 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., [email protected]: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.,
[email protected]: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;
+ }
+
+ /**
+ * Returns whether a child site lies within a parent site, which is what
makes them one site to deploy.
+ * The host must match and the child path must be the parent path or below
it.
+ * <p>
+ * Comparing host, scheme and port alone is not enough: two repositories
on the same forge, such as
+ * {@code github.com/org/parent.git} and {@code github.com/org/child.git},
share all three and are still
+ * unrelated sites.
+ *
+ * @param parentUri the site URI of the parent project
+ * @param childUri the site URI of the child project
+ * @return {@code true} if both URIs describe the same site
+ */
+ static boolean isSameSite(URI parentUri, URI childUri) {
+ if (!Objects.equals(parentUri.getHost(), childUri.getHost())) {
+ return false;
+ }
+
+ String parentPath = stripTrailingSlash(parentUri.getPath());
+ String childPath = stripTrailingSlash(childUri.getPath());
+ if (parentPath == null || childPath == null) {
+ // an opaque URI neither the SCM unwrapping nor URI parsing could
resolve; treat the sites as separate
+ return false;
+ }
+
+ // compare whole segments, so that /foo does not contain /foobar
+ return childPath.equals(parentPath) || childPath.startsWith(parentPath
+ "/");
+ }
Review Comment:
`isSameSite(..)` no longer checks scheme/port at all, whereas the previous
`URIPathDescriptor.sameSite(..)` logic did. This can cause unrelated
distribution URLs on the same host/path but different transport (e.g.
`dav:http` vs `scp`, or different explicit ports) to be treated as the same
site, potentially changing MSITE-600 behavior for non-SCM URLs. Consider making
scheme comparison conditional (skip only for SCM-derived URLs), while still
keeping a port check.
##########
src/main/java/org/apache/maven/plugins/site/deploy/AbstractDeployMojo.java:
##########
@@ -519,6 +522,101 @@ 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., [email protected]: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.,
[email protected]: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);
+
Review Comment:
The SCP-like conversion logic claims to handle bracketed IPv6 hosts, but
`indexOf(':', atIndex)` will hit the first ':' inside the IPv6 literal (e.g.
`git@[2001:db8::1]:repo`), producing an incorrect host/path split. Either
handle bracketed IPv6 explicitly or drop the IPv6 guarantee.
##########
src/main/java/org/apache/maven/plugins/site/deploy/AbstractDeployMojo.java:
##########
@@ -550,10 +648,14 @@ 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())) {
+ if (!isSameSite(siteURI.getBaseURI(), oldSiteURI.getBaseURI())) {
return oldProject;
Review Comment:
After changing `isSameSite(..)` to compare scheme conditionally, the
`getTopLevelProject(..)` call site needs to decide when to ignore scheme (SCM
URLs) vs when to keep scheme comparison (regular distributionManagement URLs).
##########
src/test/java/org/apache/maven/plugins/site/deploy/AbstractDeployMojoTest.java:
##########
@@ -0,0 +1,231 @@
+/*
+ * 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.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.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:[email protected]: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(childProject, topProject, "Top project should be child
project due to different SCM URLs");
+ }
+
+ /**
+ * 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(parentProject, topProject, "Top project should be parent
project due to same SCM base URL");
+ }
+
+ /**
+ * 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(parentProject, topProject, "Top project should be parent
project for regular URLs");
+ }
+
+ /**
+ * 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(childProject, topProject, "Top project should be child
project due to different https SCM URLs");
+ }
+
+ /**
+ * The reported case: sibling repositories on the same host. plexus-xml
inherits from plexus-pom,
+ * they share github.com, and their sites are unrelated.
+ */
+ @Test
+ public void testGetTopLevelProjectWithSiblingRepositoriesOnSameHost()
throws Exception {
+ TestDeployMojo mojo = new TestDeployMojo();
+
+ MavenProject childProject =
+ createProjectWithSite("child",
"scm:git:https://github.com/codehaus-plexus/plexus-xml.git");
+ MavenProject parentProject =
+ createProjectWithSite("parent",
"scm:git:https://github.com/codehaus-plexus/plexus-pom.git");
+
+ childProject.setParent(parentProject);
+
+ MavenProject topProject = mojo.getTopLevelProject(childProject);
+
+ assertEquals(childProject, topProject, "Sibling repositories do not
share a site");
+ }
+
+ /**
+ * 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.
+ */
Review Comment:
This test’s Javadoc still refers to `URIPathDescriptor.sameSite()` even
though the production code now uses `AbstractDeployMojo.isSameSite(..)`.
Updating the comment would avoid misleading future readers about what behavior
is being verified.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]