@@ -8,84 +8,32 @@ import org.modelix.model.persistent.CPVersion
8
8
/* *
9
9
* Was introduced in https://github.com/modelix/modelix/commit/19c74bed5921028af3ac3ee9d997fc1c4203ad44
10
10
* together with the UndoOp. The idea is that an undo should only revert changes if there is no other change that relies
11
- * on it. In that case the undo should do nothing to not indirectly undo newer changes.
11
+ * on it. In that case the undo should do nothing, to not indirectly undo newer changes.
12
12
* For example, if you added a node and someone else started changing properties on the that node, your undo should not
13
13
* remove the node to not lose the property changes.
14
+ * This requires the versions to be ordered in a way that the undo appears later.
14
15
*/
15
- class SlowLinearHistory (val baseVersionHash : String? ) {
16
-
17
- val version2descendants: MutableMap <Long , MutableSet <Long >> = HashMap ()
18
- val versions: MutableMap <Long , CLVersion > = HashMap ()
19
-
20
- /* *
21
- * Oldest version first
22
- */
23
- fun load (vararg fromVersions : CLVersion ): List <CLVersion > {
24
- for (fromVersion in fromVersions) {
25
- collect(fromVersion, emptyList())
26
- }
27
-
28
- var result: List <Long > = ArrayList ()
29
-
30
- for (version in versions.values.filter { ! it.isMerge() }.sortedBy { it.id }) {
31
- val descendantIds = version2descendants[version.id]!! .sorted().toSet()
32
- val idsInResult = result.toHashSet()
33
- if (idsInResult.contains(version.id)) {
34
- result =
35
- result +
36
- descendantIds.filter { ! idsInResult.contains(it) }
37
- } else {
38
- result =
39
- result.filter { ! descendantIds.contains(it) } +
40
- version.id +
41
- result.filter { descendantIds.contains(it) } +
42
- descendantIds.filter { ! idsInResult.contains(it) }
43
- }
44
- }
45
- return result.map { versions[it]!! }
46
- }
47
-
48
- private fun collect (version : CLVersion , path : List <CLVersion >) {
49
- if (version.hash == baseVersionHash) return
50
-
51
- if (! versions.containsKey(version.id)) versions[version.id] = version
52
- version2descendants.getOrPut(version.id) { HashSet () }.addAll(path.asSequence().map { it.id })
53
-
54
- if (version.isMerge()) {
55
- val version1 = getVersion(version.data!! .mergedVersion1!! , version.store)
56
- val version2 = getVersion(version.data!! .mergedVersion2!! , version.store)
57
- collect(version1, path)
58
- collect(version2, path)
59
- } else {
60
- val previous = version.baseVersion
61
- if (previous != null ) {
62
- collect(previous, path + version)
63
- }
64
- }
65
- }
66
-
67
- private fun getVersion (hash : KVEntryReference <CPVersion >, store : IDeserializingKeyValueStore ): CLVersion {
68
- return CLVersion (hash.getValue(store), store)
69
- }
70
- }
71
-
72
16
class LinearHistory (val baseVersionHash : String? ) {
73
17
74
18
val version2directDescendants: MutableMap <Long , Set <Long >> = HashMap ()
75
- val versions: MutableMap <Long , CLVersion > = HashMap ()
19
+ val versions: MutableMap <Long , CLVersion > = LinkedHashMap ()
76
20
77
21
/* *
78
- * Oldest version first
22
+ * @param fromVersions it is assumed that the versions are sorted by the oldest version first. When merging a new
23
+ * version into an existing one the new version should appear after the existing one. The resulting order
24
+ * will prefer existing versions to new ones, meaning during the conflict resolution the existing changes
25
+ * have a higher probability of surviving.
26
+ * @returns oldest version first
79
27
*/
80
28
fun load (vararg fromVersions : CLVersion ): List <CLVersion > {
81
29
for (fromVersion in fromVersions) {
82
- collect(fromVersion, null )
30
+ collect(fromVersion)
83
31
}
84
32
85
33
var result: List <Long > = ArrayList ()
86
34
87
35
for (version in versions.values.filter { ! it.isMerge() }.sortedBy { it.id }) {
88
- val descendantIds = HashSet < Long >(). also { collectAllDescendants(version.id, it) } .filter { ! versions[it]!! .isMerge() }.sorted().toSet()
36
+ val descendantIds = collectAllDescendants(version.id) .filter { ! versions[it]!! .isMerge() }.sorted().toSet()
89
37
val idsInResult = result.toHashSet()
90
38
if (idsInResult.contains(version.id)) {
91
39
result =
@@ -102,34 +50,45 @@ class LinearHistory(val baseVersionHash: String?) {
102
50
return result.map { versions[it]!! }
103
51
}
104
52
105
- private fun collectAllDescendants (ancestor : Long , result : MutableSet <Long >, visited : MutableSet <Long > = HashSet ()) {
106
- if (! visited.add(ancestor)) return
107
- val descendants = version2directDescendants[ancestor] ? : return
53
+ private fun collectAllDescendants (root : Long ): Set <Long > {
54
+ val result = LinkedHashSet <Long >()
55
+ var previousSize = 0
56
+ result + = root
108
57
109
- result + = descendants
110
- descendants.forEach {
111
- collectAllDescendants(it, result, visited)
112
- }
113
- }
114
-
115
- private fun collect (version : CLVersion , descendant : CLVersion ? ) {
116
- if (version.getContentHash() == baseVersionHash) return
117
- if (descendant != null ) {
118
- version2directDescendants[version.id] = (version2directDescendants[version.id] ? : emptySet()) + setOf (descendant.id)
58
+ while (previousSize != result.size) {
59
+ val nextElements = result.asSequence().drop(previousSize).toList()
60
+ previousSize = result.size
61
+ for (ancestor in nextElements) {
62
+ version2directDescendants[ancestor]?.let { result + = it }
63
+ }
119
64
}
120
- if (versions.containsKey(version.id)) return
121
65
122
- versions[version.id] = version
66
+ return result.drop(1 ).toSet()
67
+ }
123
68
124
- if (version.isMerge()) {
125
- val version1 = getVersion(version.data!! .mergedVersion1!! , version.store)
126
- val version2 = getVersion(version.data!! .mergedVersion2!! , version.store)
127
- collect(version1, version)
128
- collect(version2, version)
129
- } else {
130
- val previous = version.baseVersion
131
- if (previous != null ) {
132
- collect(previous, version)
69
+ private fun collect (root : CLVersion ) {
70
+ if (root.getContentHash() == baseVersionHash) return
71
+
72
+ var previousSize = versions.size
73
+ versions[root.id] = root
74
+
75
+ while (previousSize != versions.size) {
76
+ val nextElements = versions.asSequence().drop(previousSize).map { it.value }.toList()
77
+ previousSize = versions.size
78
+
79
+ for (descendant in nextElements) {
80
+ val ancestors = if (descendant.isMerge()) {
81
+ sequenceOf(
82
+ getVersion(descendant.data!! .mergedVersion1!! , descendant.store),
83
+ getVersion(descendant.data!! .mergedVersion2!! , descendant.store),
84
+ )
85
+ } else {
86
+ sequenceOf(descendant.baseVersion)
87
+ }.filterNotNull().filter { it.getContentHash() != baseVersionHash }.toList()
88
+ for (ancestor in ancestors) {
89
+ versions[ancestor.id] = ancestor
90
+ version2directDescendants[ancestor.id] = (version2directDescendants[ancestor.id] ? : emptySet()) + setOf (descendant.id)
91
+ }
133
92
}
134
93
}
135
94
}
0 commit comments