Skip to content

Commit 7f34450

Browse files
Merge branch '8-certain-git-histories-can-lead-to-a-stack-overflow-error' into next-release
2 parents 92359c0 + 145b5c3 commit 7f34450

3 files changed

Lines changed: 132 additions & 15 deletions

File tree

src/main/java/org/variantsync/vevos/simulation/variability/SPLCommit.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ public Optional<SPLCommit[]> parents() {
138138
return Optional.ofNullable(parents);
139139
}
140140

141-
public void setParents(final SPLCommit[] parents) {
141+
public void setParents(final SPLCommit... parents) {
142142
this.parents = parents;
143143
}
144144

src/main/java/org/variantsync/vevos/simulation/variability/sequenceextraction/LongestNonOverlappingSequences.java

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -83,29 +83,52 @@ public List<NonEmptyList<SPLCommit>> extract(final Collection<SPLCommit> commits
8383
}
8484
}
8585

86+
// Sort the sequences once more, after filtering them
87+
commitSequences.sort((o1, o2) -> Integer.compare(o2.size(), o1.size()));
8688
return commitSequences;
8789
}
8890

89-
// Recursively build sequences
9091
private static Set<LinkedList<SPLCommit>> retrieveSequencesForStart(final Map<SPLCommit, Set<SPLCommit>> parentChildMap, final SPLCommit start) {
91-
if (!parentChildMap.containsKey(start)) {
92-
// Create a new sequence that contains the start commit
93-
final Set<LinkedList<SPLCommit>> sequenceSet = new HashSet<>();
92+
// Set for holding all retrieved sequences
93+
Set<LinkedList<SPLCommit>> sequenceSet = new HashSet<>();
94+
{
95+
// We start with the 'start' commit
9496
final LinkedList<SPLCommit> sequence = new LinkedList<>();
9597
sequence.add(start);
9698
sequenceSet.add(sequence);
97-
return sequenceSet;
98-
} else {
99-
// Collect the sequences of the children and prepend the commit to each of them as parent
100-
final Set<LinkedList<SPLCommit>> sequences = new HashSet<>();
101-
for (final SPLCommit child : parentChildMap.get(start)) {
102-
final Set<LinkedList<SPLCommit>> childSequences = retrieveSequencesForStart(parentChildMap, child);
103-
for (final LinkedList<SPLCommit> childSequence : childSequences) {
104-
childSequence.addFirst(start);
105-
sequences.add(childSequence);
99+
}
100+
101+
// We continue to build sequences, going from parent to descendents, until the ends of all possible sequences have been reached
102+
boolean incomplete = true;
103+
while(incomplete) {
104+
Set<LinkedList<SPLCommit>> updatedSet = new HashSet<>();
105+
// Set to false at the start of the iteration, it is set to true if at least one sequence has been extended
106+
incomplete = false;
107+
// For all found sequences, check whether their last commit has more children that can be added to the sequence
108+
// if multiple children exist, new sequences are created
109+
for (LinkedList<SPLCommit> sequence : sequenceSet) {
110+
final SPLCommit parent = sequence.getLast();
111+
if (parentChildMap.containsKey(parent)) {
112+
final Set<SPLCommit> children = parentChildMap.get(parent);
113+
for (SPLCommit child : children) {
114+
// There is at least one child, we are not done yet
115+
incomplete = true;
116+
// Create a new sequence from the old sequence for each child
117+
final LinkedList<SPLCommit> anotherSequence = new LinkedList<>(sequence);
118+
anotherSequence.add(child);
119+
// Add the new sequence to the new set
120+
updatedSet.add(anotherSequence);
121+
}
122+
} else {
123+
// There are no children, simply add the old sequence to the updated set
124+
updatedSet.add(sequence);
106125
}
107126
}
108-
return sequences;
127+
// Update the sequence set
128+
sequenceSet = updatedSet;
109129
}
130+
131+
return sequenceSet;
110132
}
111133
}
134+
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package org.variantsync.vevos.simulation.variability.sequenceextraction;
2+
3+
import org.junit.Test;
4+
import org.variantsync.functjonal.list.NonEmptyList;
5+
import org.variantsync.vevos.simulation.variability.SPLCommit;
6+
7+
import java.util.Arrays;
8+
import java.util.HashSet;
9+
import java.util.List;
10+
import java.util.Set;
11+
12+
public class LongestNonOverlappingSequencesTest {
13+
14+
@Test
15+
public void exampleExtraction() {
16+
// For example, if the commits comprise three partially overlapping sequences ([A-B-C-D-E], [X-Y-Z], [A-B-F-G]),
17+
// the function will return the sequences ([A-B-C-D-E], [X-Y-Z], [F-G]).
18+
final SPLCommit a = new SPLCommit("A");
19+
final SPLCommit b = new SPLCommit("B");
20+
final SPLCommit c = new SPLCommit("C");
21+
final SPLCommit d = new SPLCommit("D");
22+
final SPLCommit e = new SPLCommit("E");
23+
final SPLCommit f = new SPLCommit("F");
24+
final SPLCommit g = new SPLCommit("G");
25+
final SPLCommit x = new SPLCommit("X");
26+
final SPLCommit y = new SPLCommit("Y");
27+
final SPLCommit z = new SPLCommit("Z");
28+
// A has no parent
29+
a.setParents();
30+
// A-B
31+
b.setParents(a);
32+
// B-C
33+
c.setParents(b);
34+
// C-D
35+
d.setParents(c);
36+
// D-E
37+
e.setParents(d);
38+
// B-F
39+
f.setParents(b);
40+
// F-G
41+
g.setParents(f);
42+
// X has no parent
43+
x.setParents();
44+
// X-Y
45+
y.setParents(x);
46+
// Y-Z
47+
z.setParents(y);
48+
49+
List<SPLCommit> exampleCommits = Arrays.asList(a, b, c, d, e, f, g, x, y, z);
50+
NonEmptyList<SPLCommit> expectedOne = new NonEmptyList<>(Arrays.asList(a,b,c,d,e));
51+
NonEmptyList<SPLCommit> expectedTwo = new NonEmptyList<>(Arrays.asList(x, y, z));
52+
NonEmptyList<SPLCommit> expectedThree = new NonEmptyList<>(Arrays.asList(f, g));
53+
54+
LongestNonOverlappingSequences algorithm = new LongestNonOverlappingSequences();
55+
List<NonEmptyList<SPLCommit>> result = algorithm.extract(exampleCommits);
56+
57+
assert result.size() == 3;
58+
assert sequencesAreEqual(result.get(0), expectedOne);
59+
assert sequencesAreEqual(result.get(1), expectedTwo);
60+
assert sequencesAreEqual(result.get(2), expectedThree);
61+
}
62+
63+
@Test
64+
public void stackOverflowPrevented() {
65+
int size = 10_000;
66+
Set<SPLCommit> commits = new HashSet<>();
67+
SPLCommit previousCommit = new SPLCommit("0");
68+
previousCommit.setParents();
69+
commits.add(previousCommit);
70+
for (int i = 1; i < size; i++) {
71+
SPLCommit commit = new SPLCommit(String.valueOf(i));
72+
commit.setParents(previousCommit);
73+
commits.add(commit);
74+
previousCommit = commit;
75+
}
76+
77+
LongestNonOverlappingSequences algo = new LongestNonOverlappingSequences();
78+
var result = algo.extract(commits);
79+
assert result.size() == 1;
80+
assert result.get(0).size() == size;
81+
}
82+
83+
private boolean sequencesAreEqual(final NonEmptyList<SPLCommit> a, final NonEmptyList<SPLCommit> b) {
84+
if (a.size() != b.size()) {
85+
return false;
86+
}
87+
for (int i = 0; i < a.size(); i++) {
88+
if (!a.get(i).id().equals(b.get(i).id())) {
89+
return false;
90+
}
91+
}
92+
return true;
93+
}
94+
}

0 commit comments

Comments
 (0)