This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch capable-arrhinceratops
in repository https://gitbox.apache.org/repos/asf/maven.git

commit a03e15efe7ba2fe4b710a779078ba8a268deadc4
Author: Guillaume Nodet <[email protected]>
AuthorDate: Fri Jun 19 08:36:38 2026 +0200

    [#12301] Fix StackOverflowError with internal parent and CI-friendly 
revision
    
    Re-forward-port the GH-12301 fix from maven-4.0.x to master, using
    ThreadLocal<Set<Path>> instead of the shared ConcurrentHashMap.newKeySet()
    that caused a race condition with PhasingExecutor parallel model loading.
    
    The original forward-port (#12317) used a shared set to track which POM 
files
    were being read by doReadFileModel(). This prevented the StackOverflowError
    from GH-12301, but caused false cycle detection when parallel threads in
    loadFilePom() read the same model simultaneously — breaking
    ConsumerPomBuilderTest.testSimpleConsumer.
    
    The fix uses ThreadLocal so each thread tracks only its own call stack,
    preventing actual recursive cycles within the same thread without blocking
    legitimate parallel reads across threads.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 .../maven/impl/model/DefaultModelBuilder.java      | 379 +++++++++++----------
 ...301StackOverflowInternalParentRevisionTest.java |  44 +++
 .../.mvn/.gitkeep                                  |   0
 .../parent/pom.xml                                 |  29 ++
 .../gh-12301-stackoverflow-internal-parent/pom.xml |  38 +++
 5 files changed, 313 insertions(+), 177 deletions(-)

diff --git 
a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
index 7a5b84b7ee..b331ca07d0 100644
--- 
a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
+++ 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
@@ -289,6 +289,12 @@ List<RemoteRepository> getExternalRepositories() {
         // Contains both GAV coordinates (groupId:artifactId:version) and file 
paths
         final Set<String> parentChain;
 
+        // Per-thread tracking of file paths currently being read by 
doReadFileModel.
+        // Shared across all derived sessions. Uses ThreadLocal so that 
parallel threads
+        // (e.g. from PhasingExecutor in loadFilePom) each track their own 
call stack,
+        // preventing false cycle detection across threads (GH-12301).
+        final ThreadLocal<Set<Path>> activeModelReads;
+
         ModelBuilderSessionState(ModelBuilderRequest request) {
             this(
                     request.getSession(),
@@ -299,7 +305,8 @@ List<RemoteRepository> getExternalRepositories() {
                     List.of(),
                     repos(request),
                     repos(request),
-                    new LinkedHashSet<>());
+                    new LinkedHashSet<>(),
+                    ThreadLocal.withInitial(HashSet::new));
         }
 
         static List<RemoteRepository> repos(ModelBuilderRequest request) {
@@ -319,7 +326,8 @@ private ModelBuilderSessionState(
                 List<RemoteRepository> pomRepositories,
                 List<RemoteRepository> externalRepositories,
                 List<RemoteRepository> repositories,
-                Set<String> parentChain) {
+                Set<String> parentChain,
+                ThreadLocal<Set<Path>> activeModelReads) {
             this.session = session;
             this.request = request;
             this.result = result;
@@ -329,6 +337,7 @@ private ModelBuilderSessionState(
             this.externalRepositories = externalRepositories;
             this.repositories = repositories;
             this.parentChain = parentChain;
+            this.activeModelReads = activeModelReads;
             this.result.setSource(this.request.getSource());
         }
 
@@ -379,7 +388,8 @@ ModelBuilderSessionState derive(ModelBuilderRequest 
request, DefaultModelBuilder
                     pomRepositories,
                     derivedExtRepos,
                     derivedRepos,
-                    new LinkedHashSet<>());
+                    new LinkedHashSet<>(),
+                    activeModelReads);
         }
 
         @Override
@@ -712,8 +722,12 @@ private Map<String, String> getEnhancedProperties(Model 
model, Path rootDirector
                 if (rootModelPath != null) {
                     // Check if the root model path is within the root 
directory to prevent infinite loops
                     // This can happen when a .mvn directory exists in a 
subdirectory and parent inference
-                    // tries to read models above the discovered root directory
-                    if (isParentWithinRootDirectory(rootModelPath, 
rootDirectory)) {
+                    // tries to read models above the discovered root 
directory.
+                    // Also skip if the root model is already being read on 
this thread's call stack
+                    // to prevent StackOverflowError when a project has an 
internal parent in a
+                    // subdirectory with CI-friendly ${revision} and a .mvn/ 
root marker (GH-12301).
+                    if (isParentWithinRootDirectory(rootModelPath, 
rootDirectory)
+                            && 
!activeModelReads.get().contains(rootModelPath.normalize())) {
                         Model rootModel =
                                 
derive(Sources.buildSource(rootModelPath)).readFileModel();
                         properties.putAll(getPropertiesWithProfiles(rootModel, 
properties));
@@ -1542,34 +1556,24 @@ Model doReadFileModel() throws ModelBuilderException {
             boolean rootDirectoryFromSession = false;
             setSource(modelSource.getLocation());
             logger.debug("Reading file model from " + 
modelSource.getLocation());
+            Path sourcePath = modelSource.getPath();
+            Path normalizedPath = sourcePath != null ? sourcePath.normalize() 
: null;
+            boolean trackRead = normalizedPath != null && 
activeModelReads.get().add(normalizedPath);
             try {
-                boolean strict = isBuildRequest();
                 try {
-                    rootDirectory = request.getSession().getRootDirectory();
-                    rootDirectoryFromSession = true;
-                } catch (IllegalStateException ignore) {
-                    rootDirectory = modelSource.getPath();
-                    while (rootDirectory != null && 
!Files.isDirectory(rootDirectory)) {
-                        rootDirectory = rootDirectory.getParent();
-                    }
-                }
-                try (InputStream is = modelSource.openStream()) {
-                    model = modelProcessor.read(XmlReaderRequest.builder()
-                            .strict(strict)
-                            .location(modelSource.getLocation())
-                            .modelId(modelSource.getModelId())
-                            .path(modelSource.getPath())
-                            .rootDirectory(rootDirectory)
-                            .inputStream(is)
-                            .transformer(new InterningTransformer(session))
-                            .build());
-                } catch (XmlReaderException e) {
-                    if (!strict) {
-                        throw e;
+                    boolean strict = isBuildRequest();
+                    try {
+                        rootDirectory = 
request.getSession().getRootDirectory();
+                        rootDirectoryFromSession = true;
+                    } catch (IllegalStateException ignore) {
+                        rootDirectory = modelSource.getPath();
+                        while (rootDirectory != null && 
!Files.isDirectory(rootDirectory)) {
+                            rootDirectory = rootDirectory.getParent();
+                        }
                     }
                     try (InputStream is = modelSource.openStream()) {
                         model = modelProcessor.read(XmlReaderRequest.builder()
-                                .strict(false)
+                                .strict(strict)
                                 .location(modelSource.getLocation())
                                 .modelId(modelSource.getModelId())
                                 .path(modelSource.getPath())
@@ -1577,180 +1581,201 @@ Model doReadFileModel() throws ModelBuilderException {
                                 .inputStream(is)
                                 .transformer(new InterningTransformer(session))
                                 .build());
-                    } catch (XmlReaderException ne) {
-                        // still unreadable even in non-strict mode, rethrow 
original error
-                        throw e;
-                    }
+                    } catch (XmlReaderException e) {
+                        if (!strict) {
+                            throw e;
+                        }
+                        try (InputStream is = modelSource.openStream()) {
+                            model = 
modelProcessor.read(XmlReaderRequest.builder()
+                                    .strict(false)
+                                    .location(modelSource.getLocation())
+                                    .modelId(modelSource.getModelId())
+                                    .path(modelSource.getPath())
+                                    .rootDirectory(rootDirectory)
+                                    .inputStream(is)
+                                    .transformer(new 
InterningTransformer(session))
+                                    .build());
+                        } catch (XmlReaderException ne) {
+                            // still unreadable even in non-strict mode, 
rethrow original error
+                            throw e;
+                        }
 
+                        add(
+                                Severity.ERROR,
+                                Version.V20,
+                                "Malformed POM " + modelSource.getLocation() + 
": " + e.getMessage(),
+                                e);
+                    }
+                } catch (XmlReaderException e) {
                     add(
-                            Severity.ERROR,
-                            Version.V20,
-                            "Malformed POM " + modelSource.getLocation() + ": 
" + e.getMessage(),
+                            Severity.FATAL,
+                            Version.BASE,
+                            "Non-parseable POM " + modelSource.getLocation() + 
": " + e.getMessage(),
                             e);
-                }
-            } catch (XmlReaderException e) {
-                add(
-                        Severity.FATAL,
-                        Version.BASE,
-                        "Non-parseable POM " + modelSource.getLocation() + ": 
" + e.getMessage(),
-                        e);
-                throw newModelBuilderException();
-            } catch (IOException e) {
-                String msg = e.getMessage();
-                if (msg == null || msg.isEmpty()) {
-                    // NOTE: There's java.nio.charset.MalformedInputException 
and sun.io.MalformedInputException
-                    if 
(e.getClass().getName().endsWith("MalformedInputException")) {
-                        msg = "Some input bytes do not match the file 
encoding.";
-                    } else {
-                        msg = e.getClass().getSimpleName();
+                    throw newModelBuilderException();
+                } catch (IOException e) {
+                    String msg = e.getMessage();
+                    if (msg == null || msg.isEmpty()) {
+                        // NOTE: There's 
java.nio.charset.MalformedInputException and sun.io.MalformedInputException
+                        if 
(e.getClass().getName().endsWith("MalformedInputException")) {
+                            msg = "Some input bytes do not match the file 
encoding.";
+                        } else {
+                            msg = e.getClass().getSimpleName();
+                        }
                     }
+                    add(Severity.FATAL, Version.BASE, "Non-readable POM " + 
modelSource.getLocation() + ": " + msg, e);
+                    throw newModelBuilderException();
                 }
-                add(Severity.FATAL, Version.BASE, "Non-readable POM " + 
modelSource.getLocation() + ": " + msg, e);
-                throw newModelBuilderException();
-            }
 
-            if (model.getModelVersion() == null) {
-                String namespace = model.getNamespaceUri();
-                if (namespace != null && 
namespace.startsWith(NAMESPACE_PREFIX)) {
-                    model = 
model.withModelVersion(namespace.substring(NAMESPACE_PREFIX.length()));
+                if (model.getModelVersion() == null) {
+                    String namespace = model.getNamespaceUri();
+                    if (namespace != null && 
namespace.startsWith(NAMESPACE_PREFIX)) {
+                        model = 
model.withModelVersion(namespace.substring(NAMESPACE_PREFIX.length()));
+                    }
                 }
-            }
 
-            if (isBuildRequest()) {
-                model = model.withPomFile(modelSource.getPath());
-
-                Parent parent = model.getParent();
-                if (parent != null) {
-                    String groupId = parent.getGroupId();
-                    String artifactId = parent.getArtifactId();
-                    String version = parent.getVersion();
-                    String path = parent.getRelativePath();
-                    boolean versionContainsExpression = version != null && 
version.contains("${");
-                    if ((groupId == null || artifactId == null || version == 
null || versionContainsExpression)
-                            && (path == null || !path.isEmpty())) {
-                        Path pomFile = model.getPomFile();
-                        Path relativePath = Paths.get(path != null ? path : 
"..");
-                        Path pomPath = 
pomFile.resolveSibling(relativePath).normalize();
-                        if (Files.isDirectory(pomPath)) {
-                            pomPath = 
modelProcessor.locateExistingPom(pomPath);
-                        }
-                        if (pomPath != null && Files.isRegularFile(pomPath)) {
-                            if (rootDirectoryFromSession && 
!isParentWithinRootDirectory(pomPath, rootDirectory)) {
-                                add(
-                                        Severity.FATAL,
-                                        Version.BASE,
-                                        "Parent POM " + pomPath + " is located 
above the root directory "
-                                                + rootDirectory
-                                                + ". This setup is invalid 
when a .mvn directory exists in a subdirectory.",
-                                        parent.getLocation("relativePath"));
-                                throw newModelBuilderException();
+                if (isBuildRequest()) {
+                    model = model.withPomFile(modelSource.getPath());
+
+                    Parent parent = model.getParent();
+                    if (parent != null) {
+                        String groupId = parent.getGroupId();
+                        String artifactId = parent.getArtifactId();
+                        String version = parent.getVersion();
+                        String path = parent.getRelativePath();
+                        boolean versionContainsExpression = version != null && 
version.contains("${");
+                        if ((groupId == null || artifactId == null || version 
== null || versionContainsExpression)
+                                && (path == null || !path.isEmpty())) {
+                            Path pomFile = model.getPomFile();
+                            Path relativePath = Paths.get(path != null ? path 
: "..");
+                            Path pomPath = 
pomFile.resolveSibling(relativePath).normalize();
+                            if (Files.isDirectory(pomPath)) {
+                                pomPath = 
modelProcessor.locateExistingPom(pomPath);
                             }
+                            if (pomPath != null && 
Files.isRegularFile(pomPath)) {
+                                if (rootDirectoryFromSession && 
!isParentWithinRootDirectory(pomPath, rootDirectory)) {
+                                    add(
+                                            Severity.FATAL,
+                                            Version.BASE,
+                                            "Parent POM " + pomPath + " is 
located above the root directory "
+                                                    + rootDirectory
+                                                    + ". This setup is invalid 
when a .mvn directory exists in a subdirectory.",
+                                            
parent.getLocation("relativePath"));
+                                    throw newModelBuilderException();
+                                }
 
-                            Model parentModel =
-                                    
derive(Sources.buildSource(pomPath)).readFileModel();
-                            String parentGroupId = getGroupId(parentModel);
-                            String parentArtifactId = 
parentModel.getArtifactId();
-                            String parentVersion = getVersion(parentModel);
-                            if ((groupId == null || 
groupId.equals(parentGroupId))
-                                    && (artifactId == null || 
artifactId.equals(parentArtifactId))
-                                    && (version == null
-                                            || version.equals(parentVersion)
-                                            || versionContainsExpression)) {
-                                model = model.withParent(parent.with()
-                                        .groupId(parentGroupId)
-                                        .artifactId(parentArtifactId)
-                                        .version(parentVersion)
-                                        .build());
+                                Model parentModel =
+                                        
derive(Sources.buildSource(pomPath)).readFileModel();
+                                String parentGroupId = getGroupId(parentModel);
+                                String parentArtifactId = 
parentModel.getArtifactId();
+                                String parentVersion = getVersion(parentModel);
+                                if ((groupId == null || 
groupId.equals(parentGroupId))
+                                        && (artifactId == null || 
artifactId.equals(parentArtifactId))
+                                        && (version == null
+                                                || 
version.equals(parentVersion)
+                                                || versionContainsExpression)) 
{
+                                    model = model.withParent(parent.with()
+                                            .groupId(parentGroupId)
+                                            .artifactId(parentArtifactId)
+                                            .version(parentVersion)
+                                            .build());
+                                } else {
+                                    mismatchRelativePathAndGA(model, parent, 
parentGroupId, parentArtifactId);
+                                }
                             } else {
-                                mismatchRelativePathAndGA(model, parent, 
parentGroupId, parentArtifactId);
-                            }
-                        } else {
-                            if 
(!MODEL_VERSION_4_0_0.equals(model.getModelVersion()) && path != null) {
-                                wrongParentRelativePath(model);
+                                if 
(!MODEL_VERSION_4_0_0.equals(model.getModelVersion()) && path != null) {
+                                    wrongParentRelativePath(model);
+                                }
                             }
                         }
                     }
-                }
 
-                // subprojects discovery
-                if (!hasSubprojectsDefined(model)
-                        // only discover subprojects if POM > 4.0.0
-                        && !MODEL_VERSION_4_0_0.equals(model.getModelVersion())
-                        // and if packaging is POM (we check type, but the 
session is not yet available,
-                        // we would require the project realm if we want to 
support extensions
-                        && Type.POM.equals(model.getPackaging())) {
-                    List<String> subprojects = new ArrayList<>();
-                    try (Stream<Path> files = 
Files.list(model.getProjectDirectory())) {
-                        for (Path f : files.toList()) {
-                            if (Files.isDirectory(f)) {
-                                Path subproject = 
modelProcessor.locateExistingPom(f);
-                                if (subproject != null) {
-                                    
subprojects.add(f.getFileName().toString());
+                    // subprojects discovery
+                    if (!hasSubprojectsDefined(model)
+                            // only discover subprojects if POM > 4.0.0
+                            && 
!MODEL_VERSION_4_0_0.equals(model.getModelVersion())
+                            // and if packaging is POM (we check type, but the 
session is not yet available,
+                            // we would require the project realm if we want 
to support extensions
+                            && Type.POM.equals(model.getPackaging())) {
+                        List<String> subprojects = new ArrayList<>();
+                        try (Stream<Path> files = 
Files.list(model.getProjectDirectory())) {
+                            for (Path f : files.toList()) {
+                                if (Files.isDirectory(f)) {
+                                    Path subproject = 
modelProcessor.locateExistingPom(f);
+                                    if (subproject != null) {
+                                        
subprojects.add(f.getFileName().toString());
+                                    }
                                 }
                             }
+                            if (!subprojects.isEmpty()) {
+                                model = model.withSubprojects(subprojects);
+                            }
+                        } catch (IOException e) {
+                            add(Severity.FATAL, Version.V41, "Error 
discovering subprojects", e);
                         }
-                        if (!subprojects.isEmpty()) {
-                            model = model.withSubprojects(subprojects);
-                        }
-                    } catch (IOException e) {
-                        add(Severity.FATAL, Version.V41, "Error discovering 
subprojects", e);
                     }
-                }
 
-                // Enhanced property resolution with profile activation for 
CI-friendly versions and repository URLs
-                // This includes directory properties, profile properties, and 
user properties
-                Map<String, String> properties = getEnhancedProperties(model, 
rootDirectory);
-
-                // CI friendly version processing with profile-aware properties
-                model = model.with()
-                        .version(replaceCiFriendlyVersion(properties, 
model.getVersion()))
-                        .parent(
-                                model.getParent() != null
-                                        ? model.getParent()
-                                                
.withVersion(replaceCiFriendlyVersion(
-                                                        properties,
-                                                        
model.getParent().getVersion()))
-                                        : null)
-                        .build();
+                    // Enhanced property resolution with profile activation 
for CI-friendly versions and repository URLs
+                    // This includes directory properties, profile properties, 
and user properties
+                    Map<String, String> properties = 
getEnhancedProperties(model, rootDirectory);
+
+                    // CI friendly version processing with profile-aware 
properties
+                    model = model.with()
+                            .version(replaceCiFriendlyVersion(properties, 
model.getVersion()))
+                            .parent(
+                                    model.getParent() != null
+                                            ? model.getParent()
+                                                    
.withVersion(replaceCiFriendlyVersion(
+                                                            properties,
+                                                            
model.getParent().getVersion()))
+                                            : null)
+                            .build();
 
-                // Repository URL interpolation with the same profile-aware 
properties
-                UnaryOperator<String> callback = properties::get;
-                model = model.with()
-                        
.repositories(interpolateRepository(model.getRepositories(), callback))
-                        
.pluginRepositories(interpolateRepository(model.getPluginRepositories(), 
callback))
-                        .profiles(map(model.getProfiles(), 
this::interpolateRepository, callback))
-                        
.distributionManagement(interpolateRepository(model.getDistributionManagement(),
 callback))
-                        .build();
-                // Override model properties with user properties (use request 
properties
-                // to ensure consistency with model interpolation)
-                Map<String, String> userProps = request.getUserProperties();
-                Map<String, String> newProps = merge(model.getProperties(), 
userProps);
-                if (newProps != null) {
-                    model = model.withProperties(newProps);
+                    // Repository URL interpolation with the same 
profile-aware properties
+                    UnaryOperator<String> callback = properties::get;
+                    model = model.with()
+                            
.repositories(interpolateRepository(model.getRepositories(), callback))
+                            
.pluginRepositories(interpolateRepository(model.getPluginRepositories(), 
callback))
+                            .profiles(map(model.getProfiles(), 
this::interpolateRepository, callback))
+                            
.distributionManagement(interpolateRepository(model.getDistributionManagement(),
 callback))
+                            .build();
+                    // Override model properties with user properties (use 
request properties
+                    // to ensure consistency with model interpolation)
+                    Map<String, String> userProps = 
request.getUserProperties();
+                    Map<String, String> newProps = 
merge(model.getProperties(), userProps);
+                    if (newProps != null) {
+                        model = model.withProperties(newProps);
+                    }
+                    model = model.withProfiles(merge(model.getProfiles(), 
userProps));
                 }
-                model = model.withProfiles(merge(model.getProfiles(), 
userProps));
-            }
 
-            for (var transformer : transformers) {
-                model = transformer.transformFileModel(model);
-            }
+                for (var transformer : transformers) {
+                    model = transformer.transformFileModel(model);
+                }
 
-            setSource(model);
-            modelValidator.validateFileModel(
-                    session,
-                    model,
-                    isBuildRequest() ? ModelValidator.VALIDATION_LEVEL_STRICT 
: ModelValidator.VALIDATION_LEVEL_MINIMAL,
-                    this);
-            InternalSession internalSession = InternalSession.from(session);
-            if 
(Features.mavenMaven3Personality(internalSession.getSession().getConfigProperties())
-                    && Objects.equals(ModelBuilder.MODEL_VERSION_4_1_0, 
model.getModelVersion())) {
-                add(Severity.FATAL, Version.BASE, "Maven3 mode: no higher 
model version than 4.0.0 allowed");
-            }
-            if (hasFatalErrors()) {
-                throw newModelBuilderException();
-            }
+                setSource(model);
+                modelValidator.validateFileModel(
+                        session,
+                        model,
+                        isBuildRequest()
+                                ? ModelValidator.VALIDATION_LEVEL_STRICT
+                                : ModelValidator.VALIDATION_LEVEL_MINIMAL,
+                        this);
+                InternalSession internalSession = 
InternalSession.from(session);
+                if 
(Features.mavenMaven3Personality(internalSession.getSession().getConfigProperties())
+                        && Objects.equals(ModelBuilder.MODEL_VERSION_4_1_0, 
model.getModelVersion())) {
+                    add(Severity.FATAL, Version.BASE, "Maven3 mode: no higher 
model version than 4.0.0 allowed");
+                }
+                if (hasFatalErrors()) {
+                    throw newModelBuilderException();
+                }
 
-            return model;
+                return model;
+            } finally {
+                if (trackRead) {
+                    activeModelReads.get().remove(normalizedPath);
+                }
+            }
         }
 
         private DistributionManagement interpolateRepository(
diff --git 
a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12301StackOverflowInternalParentRevisionTest.java
 
b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12301StackOverflowInternalParentRevisionTest.java
new file mode 100644
index 0000000000..dd24924ad1
--- /dev/null
+++ 
b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12301StackOverflowInternalParentRevisionTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.it;
+
+import java.io.File;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * This is a test set for <a 
href="https://github.com/apache/maven/issues/12301";>Issue #12301</a>.
+ * Verifies that a project with an internal parent in a subdirectory using 
CI-friendly
+ * {@code ${revision}} and a {@code .mvn/} root marker does not cause a 
StackOverflowError.
+ */
+public class MavenITgh12301StackOverflowInternalParentRevisionTest extends 
AbstractMavenIntegrationTestCase {
+
+    @Test
+    public void testNoStackOverflowWithInternalParentAndRevision() throws 
Exception {
+        File testDir = 
extractResources("/gh-12301-stackoverflow-internal-parent");
+
+        Verifier verifier = newVerifier(testDir.getAbsolutePath());
+        verifier.setAutoclean(false);
+        verifier.deleteArtifacts("org.apache.maven.its.gh12301");
+        verifier.addCliArgument("-Drevision=1.0-SNAPSHOT");
+        verifier.addCliArgument("validate");
+        verifier.execute();
+        verifier.verifyErrorFreeLog();
+    }
+}
diff --git 
a/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/.mvn/.gitkeep
 
b/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/.mvn/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git 
a/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/parent/pom.xml
 
b/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/parent/pom.xml
new file mode 100644
index 0000000000..940ecc2231
--- /dev/null
+++ 
b/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/parent/pom.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+  <modelVersion>4.1.0</modelVersion>
+
+  <groupId>org.apache.maven.its.gh12301</groupId>
+  <artifactId>parent</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
+
+  <name>Maven Integration Test :: GH-12301 :: Parent</name>
+</project>
diff --git 
a/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/pom.xml
 
b/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/pom.xml
new file mode 100644
index 0000000000..e3a1d65be1
--- /dev/null
+++ 
b/its/core-it-suite/src/test/resources/gh-12301-stackoverflow-internal-parent/pom.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+  <modelVersion>4.1.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.maven.its.gh12301</groupId>
+    <artifactId>parent</artifactId>
+    <version>${revision}</version>
+    <relativePath>parent</relativePath>
+  </parent>
+
+  <artifactId>root</artifactId>
+  <packaging>pom</packaging>
+
+  <name>Maven Integration Test :: GH-12301 :: Root</name>
+
+  <properties>
+    <revision>1.0-SNAPSHOT</revision>
+  </properties>
+</project>


Reply via email to