Skip to content

Commit 08cfbfb

Browse files
committed
fix(model-datastructure): UndoOp reverted unrelated changes
If a branch did some changes and then directly reverted them, you would expect that merging it has no effect on the resulting model, but it actually reverted conflicting changes from the other branch. There was already a test for that, but the number of changes it applied was to small to create such conflicts.
1 parent d8a37be commit 08cfbfb

File tree

2 files changed

+29
-6
lines changed

2 files changed

+29
-6
lines changed

model-datastructure/src/commonMain/kotlin/org/modelix/model/VersionMerger.kt

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import org.modelix.model.lazy.CLVersion
2424
import org.modelix.model.lazy.IDeserializingKeyValueStore
2525
import org.modelix.model.operations.IOperation
2626
import org.modelix.model.operations.IOperationIntend
27+
import org.modelix.model.operations.UndoOp
2728
import org.modelix.model.persistent.CPVersion
2829

2930
class VersionMerger(private val storeCache: IDeserializingKeyValueStore, private val idGenerator: IIdGenerator) {
@@ -70,7 +71,7 @@ class VersionMerger(private val storeCache: IDeserializingKeyValueStore, private
7071
return if (leftVersion.id < rightVersion.id) leftVersion else rightVersion
7172
}
7273

73-
val versionsToApply = LinearHistory(commonBase?.hash).load(leftVersion, rightVersion)
74+
val versionsToApply = filterUndo(LinearHistory(commonBase?.hash).load(leftVersion, rightVersion))
7475

7576
val operationsToApply = versionsToApply.flatMap { captureIntend(it) }
7677
var mergedVersion: CLVersion? = null
@@ -116,6 +117,24 @@ class VersionMerger(private val storeCache: IDeserializingKeyValueStore, private
116117
return mergedVersion!!
117118
}
118119

120+
/**
121+
* Instead of computing and applying the inverse operation we can optimize for the case when an undo follows
122+
* directly after the version to be undone and just drop the version.
123+
*/
124+
private fun filterUndo(versions: List<CLVersion>): List<CLVersion> {
125+
val filtered = versions.toMutableList()
126+
for (i in (0 until filtered.lastIndex).reversed()) {
127+
val v0 = filtered[i]
128+
val v1 = filtered.getOrNull(i + 1) ?: continue
129+
130+
if (v1.numberOfOperations == 1 && (v1.operations.single() as? UndoOp)?.versionHash?.getHash() == v0.getContentHash()) {
131+
filtered.removeAt(i)
132+
filtered.removeAt(i)
133+
}
134+
}
135+
return filtered
136+
}
137+
119138
private fun captureIntend(version: CLVersion): List<IOperationIntend> {
120139
val operations = version.operations.toList()
121140
if (operations.isEmpty()) return listOf()

model-datastructure/src/commonTest/kotlin/UndoTest.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,14 @@ class UndoTest {
6767
val baseBranch = OTBranch(PBranch(CLTree(store), idGenerator), idGenerator, store)
6868
val rand = Random(347663)
6969

70-
randomChanges(baseBranch, 5, idGenerator, rand)
70+
randomChanges(baseBranch, 50, idGenerator, rand)
7171
val baseVersion = createVersion(baseBranch.operationsAndTree, null, versionIdGenerator, store)
7272

7373
val maxIndex = 2
7474
val branches = (0..maxIndex).map { OTBranch(PBranch(baseVersion.tree, idGenerator), idGenerator, store) }.toList()
7575
for (i in 0..maxIndex) {
7676
branches[i].runWrite {
77-
randomChanges(branches[i], 5, idGenerator, rand)
77+
randomChanges(branches[i], 50, idGenerator, rand)
7878
}
7979
}
8080
val versions = branches.map { branch ->
@@ -108,14 +108,14 @@ class UndoTest {
108108
val baseBranch = OTBranch(PBranch(CLTree(store), idGenerator), idGenerator, store)
109109
val rand = Random(347663)
110110

111-
randomChanges(baseBranch, 5, idGenerator, rand)
111+
randomChanges(baseBranch, 50, idGenerator, rand)
112112
val baseVersion = createVersion(baseBranch.operationsAndTree, null, versionIdGenerator, store)
113113

114114
val maxIndex = 2
115115
val branches = (0..maxIndex).map { OTBranch(PBranch(baseVersion.tree, idGenerator), idGenerator, store) }.toList()
116116
for (i in 0..maxIndex) {
117117
branches[i].runWrite {
118-
randomChanges(branches[i], 5, idGenerator, rand)
118+
randomChanges(branches[i], 50, idGenerator, rand)
119119
}
120120
}
121121
val versions = branches.map { branch ->
@@ -162,9 +162,13 @@ class UndoTest {
162162
private fun randomChanges(baseBranch: OTBranch, numChanges: Int, idGenerator: IIdGenerator, rand: Random) {
163163
baseBranch.runWrite {
164164
val changeGenerator = RandomTreeChangeGenerator(idGenerator, rand).growingOperationsOnly()
165-
for (i in 0 until numChanges) {
165+
for (i in 0 until (numChanges / 2)) {
166166
changeGenerator.applyRandomChange(baseBranch, null)
167167
}
168+
val changeGenerator2 = RandomTreeChangeGenerator(idGenerator, rand)
169+
for (i in 0 until (numChanges / 2)) {
170+
changeGenerator2.applyRandomChange(baseBranch, null)
171+
}
168172
}
169173
}
170174

0 commit comments

Comments
 (0)