Skip to content

Commit 5107f47

Browse files
authored
Deduplicate filtered dependency graph (#2489)
* Deduplicate filtered dependency graph * Add regression test
1 parent 69267a1 commit 5107f47

File tree

2 files changed

+56
-1
lines changed

2 files changed

+56
-1
lines changed

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
@@ -149,7 +149,24 @@ private List<MavenProject> applyFilter(
149149
filtered.addAll(upstream ? getUpstreamProjects(project, false) : getDownstreamProjects(project, false));
150150
}
151151
}
152-
return filtered;
152+
if (filtered.isEmpty() || filtered.size() == 1) {
153+
// Optimization to skip streaming, distincting, and collecting to a new list when there is zero or one
154+
// project, aka there can't be duplicates.
155+
return filtered;
156+
}
157+
158+
// Distinct the projects to avoid duplicates. Duplicates are possible in multi-module projects.
159+
//
160+
// Given a scenario where there is an aggregate POM with modules A, B, C, D, and E and project E depends on
161+
// A, B, C, and D. If the aggregate POM is being filtered for non-transitive and downstream dependencies where
162+
// only A, C, and E are whitelisted duplicates will occur. When scanning projects A, C, and E, those will be
163+
// added to 'filtered' as they are whitelisted. When scanning B and D, they are not whitelisted, and since
164+
// transitive is false, their downstream dependencies will be added to 'filtered'. E is a downstream dependency
165+
// of A, B, C, and D, so when scanning B and D, E will be added again 'filtered'.
166+
//
167+
// Without de-duplication, the final list would contain E three times, once for E being in the projects and
168+
// whitelisted, and twice more for E being a downstream dependency of B and D.
169+
return filtered.stream().distinct().collect(Collectors.toList());
153170
}
154171

155172
@Override

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ public class DefaultProjectDependencyGraphTest extends TestCase {
3939

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

42+
private final MavenProject dProject = createProject(
43+
Arrays.asList(toDependency(aProject), toDependency(bProject), toDependency(cProject)), "dProject");
44+
45+
private final MavenProject eProject = createProject(
46+
Arrays.asList(toDependency(aProject), toDependency(bProject), toDependency(cProject), toDependency(dProject)),
47+
"eProject");
48+
4249
private final MavenProject depender1 = createProject(Arrays.asList(toDependency(aProject)), "depender1");
4350

4451
private final MavenProject depender2 = createProject(Arrays.asList(toDependency(aProject)), "depender2");
@@ -61,6 +68,37 @@ public void testNonTransitiveFiltering() throws DuplicateProjectException, Cycle
6168
assertTrue(graph.getDownstreamProjects(aProject, false).contains(cProject));
6269
}
6370

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

0 commit comments

Comments
 (0)