Skip to content

Commit 4e02a89

Browse files
authored
Deduplicate filtered dependency graph (#2493)
Port of #2489 against master. Fixes #2487
1 parent 6d34ba9 commit 4e02a89

3 files changed

Lines changed: 59 additions & 2 deletions

File tree

impl/maven-core/src/main/java/org/apache/maven/graph/FilteredProjectDependencyGraph.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,24 @@ private List<MavenProject> applyFilter(
123123
filtered.addAll(upstream ? getUpstreamProjects(project, false) : getDownstreamProjects(project, false));
124124
}
125125
}
126-
return filtered;
126+
if (filtered.isEmpty() || filtered.size() == 1) {
127+
// Optimization to skip streaming, distincting, and collecting to a new list when there is zero or one
128+
// project, aka there can't be duplicates.
129+
return filtered;
130+
}
131+
132+
// Distinct the projects to avoid duplicates. Duplicates are possible in multi-module projects.
133+
//
134+
// Given a scenario where there is an aggregate POM with modules A, B, C, D, and E and project E depends on
135+
// A, B, C, and D. If the aggregate POM is being filtered for non-transitive and downstream dependencies where
136+
// only A, C, and E are whitelisted duplicates will occur. When scanning projects A, C, and E, those will be
137+
// added to 'filtered' as they are whitelisted. When scanning B and D, they are not whitelisted, and since
138+
// transitive is false, their downstream dependencies will be added to 'filtered'. E is a downstream dependency
139+
// of A, B, C, and D, so when scanning B and D, E will be added again 'filtered'.
140+
//
141+
// Without de-duplication, the final list would contain E three times, once for E being in the projects and
142+
// whitelisted, and twice more for E being a downstream dependency of B and D.
143+
return filtered.stream().distinct().toList();
127144
}
128145

129146
@Override

impl/maven-core/src/test/java/org/apache/maven/graph/DefaultProjectDependencyGraphTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ class DefaultProjectDependencyGraphTest {
4141

4242
private final MavenProject cProject = createProject(Arrays.asList(toDependency(bProject)), "cProject");
4343

44+
private final MavenProject dProject = createProject(
45+
Arrays.asList(toDependency(aProject), toDependency(bProject), toDependency(cProject)), "dProject");
46+
47+
private final MavenProject eProject = createProject(
48+
Arrays.asList(
49+
toDependency(aProject), toDependency(bProject), toDependency(cProject), toDependency(dProject)),
50+
"eProject");
51+
4452
private final MavenProject depender1 = createProject(Arrays.asList(toDependency(aProject)), "depender1");
4553

4654
private final MavenProject depender2 = createProject(Arrays.asList(toDependency(aProject)), "depender2");
@@ -64,6 +72,38 @@ void testNonTransitiveFiltering() throws DuplicateProjectException, CycleDetecte
6472
assertTrue(graph.getDownstreamProjects(aProject, false).contains(cProject));
6573
}
6674

75+
// Test verifying that getDownstreamProjects does not contain duplicates.
76+
// This is a regression test for https://github.com/apache/maven/issues/2487.
77+
//
78+
// The graph is:
79+
// aProject -> bProject
80+
// | -> dProject
81+
// | -> eProject
82+
// bProject -> cProject
83+
// | -> dProject
84+
// | -> eProject
85+
// cProject -> dProject
86+
// | -> eProject
87+
// dProject -> eProject
88+
//
89+
// When getting the non-transitive, downstream projects of aProject with a whitelist of aProject, dProject,
90+
// and eProject, we expect to get dProject, and eProject with no duplicates.
91+
// Before the fix, this would return dProject and eProject twice, once from bProject and once from cProject. As
92+
// aProject is whitelisted, it should not be returned as a downstream project for itself. bProject and cProject
93+
// are not whitelisted, so they should return their downstream projects, both have dProject and eProject as
94+
// downstream projects. Which would result in dProject and eProject being returned twice, but now the results are
95+
// made unique.
96+
@Test
97+
public void testGetDownstreamDoesNotDuplicateProjects() throws CycleDetectedException, DuplicateProjectException {
98+
ProjectDependencyGraph graph =
99+
new DefaultProjectDependencyGraph(Arrays.asList(aProject, bProject, cProject, dProject, eProject));
100+
graph = new FilteredProjectDependencyGraph(graph, Arrays.asList(aProject, dProject, eProject));
101+
final List<MavenProject> downstreamProjects = graph.getDownstreamProjects(aProject, false);
102+
assertEquals(2, downstreamProjects.size());
103+
assertTrue(downstreamProjects.contains(dProject));
104+
assertTrue(downstreamProjects.contains(eProject));
105+
}
106+
67107
@Test
68108
void testGetSortedProjects() throws DuplicateProjectException, CycleDetectedException {
69109
ProjectDependencyGraph graph = new DefaultProjectDependencyGraph(Arrays.asList(depender1, aProject));

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ under the License.
140140
<distributionShortName>Maven</distributionShortName>
141141
<distributionName>Apache Maven</distributionName>
142142
<maven.site.path>ref/4-LATEST</maven.site.path>
143-
<project.build.outputTimestamp>2025-03-05T09:43:59Z</project.build.outputTimestamp>
143+
<project.build.outputTimestamp>2025-06-18T10:29:55Z</project.build.outputTimestamp>
144144
<!-- various versions -->
145145
<assertjVersion>3.27.3</assertjVersion>
146146
<asmVersion>9.8</asmVersion>

0 commit comments

Comments
 (0)