added mrged blocks and README.md

This commit is contained in:
Sergey Chernov 2023-03-21 04:09:20 +01:00
parent 3f6a16224e
commit 2262c66b63
5 changed files with 2065 additions and 16 deletions

View File

@ -2,6 +2,37 @@
Tho tools to make smart merge of two versions of changed original data. See [merge3] function online docs.
## Usage
Add dependency:
~~~kotlin
repositories {
//...
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
}
~~~
and in the source set dependencies something like
~~~kotlin
dependencies {
// ...
implementation("net.sergeych:mp_diff:0.0.1-SNAPSHOT")
}
~~~
Now call it in the code
~~~kotlin
val src = "Hello world".toList()
val a = "Hello friend".toList()
val b = "Bye world".toList()
val m = merge3(src, b, a)
assertEquals("Bye friend", m.merged.joinToString(""))
println(m.blocks)
~~~
## Acknowledgments

View File

@ -1,5 +1,6 @@
plugins {
kotlin("multiplatform") version "1.8.0"
`maven-publish`
}
group = "net.sergeych"
@ -57,4 +58,23 @@ kotlin {
val nativeMain by getting
val nativeTest by getting
}
publishing {
val mavenToken by lazy {
File("${System.getProperty("user.home")}/.gitea_token").readText()
}
repositories {
maven {
credentials(HttpHeaderCredentials::class) {
name = "Authorization"
value = mavenToken
}
url = uri("https://gitea.sergeych.net/api/packages/cloudoc.ru/maven")
authentication {
create("Authorization", HttpHeaderAuthentication::class)
}
}
}
}
}

1944
kotlin-js-store/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -11,14 +11,68 @@ import net.sergeych.mp_logger.debug
import net.sergeych.sprintf.sprintf
/**
* Merge result. This version does not report (yet) conflicts, just merges it all.
* @param merged the best merged resut. In the case of the conflict usually has both variants concatennated in place
* Merge result. This version does not report (yet) conflicts, just merges it all. See also [blocks] for
* another representation of the merged data.
*
* @param merged the best merged data. In the case of the conflict usually has both variants concatennated in place
* @param changedAreas ranges where data were altered. could be used to highlight changes
*/
class MergeResult<T>(
val merged: List<T>,
val changedAreas: List<ClosedRange<Int>>,
)
val changedAreas: List<IntRange>,
) {
val blocks: List<MergedBlock<T>> by lazy {
if (changedAreas.isEmpty())
listOf(MergedBlock.Unchanged(merged))
else {
val result = mutableListOf<MergedBlock<T>>()
var start = 0
for (r in changedAreas) {
if (start != r.start) {
result.add(MergedBlock.Unchanged(merged.slice(start until r.start)))
}
if (!r.isEmpty()) {
result.add(MergedBlock.Resolved(merged.slice(r)))
}
start = r.endInclusive + 1
}
if (start < merged.size) {
result.add(MergedBlock.Unchanged(merged.slice(start until merged.size)))
}
result
}
}
}
/**
* Merge3 sequential result block.
*/
sealed class MergedBlock<T> {
/**
* Merged data. Int the case of the conflict, the concatenated conflicting data
*/
abstract val data: List<T>
/**
* Data that are equals in both variants and therefore was not altered
*/
data class Unchanged<T>(override val data: List<T>) : MergedBlock<T>()
/**
* The portion of data that was merged without conflicts
*/
data class Resolved<T>(override val data: List<T>) : MergedBlock<T>()
/**
* The portion of data that can't be merged due to the conflicting changes.
* __Note it is not yet used.__
*/
@Suppress("unused")
data class Conflict<T>(val variantA: List<T>, val variantB: List<T>) : MergedBlock<T>() {
override val data = variantA + variantB
}
}
/**
* Perform 3-way merge. See [merge3] for details.
@ -98,8 +152,7 @@ private class Merge3<T>(val source: List<T>, val variantA: List<T>, val variantB
}
}
private val conflicts = mutableListOf<ClosedRange<Int>>()
private val updates = mutableListOf<ClosedRange<Int>>()
private val updates = mutableListOf<IntRange>()
private fun applyDelta(delta: AbstractDelta<T>) {
when (delta) {
@ -149,12 +202,12 @@ private class Merge3<T>(val source: List<T>, val variantA: List<T>, val variantB
// detect ranges
var conflictStart = -1
var changeStart = -1
fun closeConflict(index: Int) {
if (conflictStart >= 0) {
conflicts.add(conflictStart until index)
conflictStart = -1
}
}
// fun closeConflict(index: Int) {
// if (conflictStart >= 0) {
// conflicts.add(conflictStart until index)
// conflictStart = -1
// }
// }
fun closeUpdate(index: Int) {
if (changeStart >= 0) {
@ -167,13 +220,13 @@ private class Merge3<T>(val source: List<T>, val variantA: List<T>, val variantB
when (count) {
0 -> {
// close conflict or update if was open
closeConflict(index)
// closeConflict(index)
closeUpdate(index)
}
1 -> {
// could be end of the conflict
closeConflict(index)
// closeConflict(index)
// open change if not opened
if (changeStart < 0) changeStart = index
}
@ -191,7 +244,7 @@ private class Merge3<T>(val source: List<T>, val variantA: List<T>, val variantB
}
}
}
closeConflict(changeCount.size)
// closeConflict(changeCount.size)
closeUpdate(changeCount.size)
return MergeResult<T>(result, updates)
@ -205,5 +258,5 @@ private class Merge3<T>(val source: List<T>, val variantA: List<T>, val variantB
*/
fun <T> merge3(source: List<T>, a: List<T>, b: List<T>, showDebug: Boolean = false): MergeResult<T> =
Merge3(source, a, b).also {
if( showDebug ) it.logLevel = Log.Level.DEBUG
if (showDebug) it.logLevel = Log.Level.DEBUG
}.perform()

View File

@ -32,6 +32,7 @@ class BasicTest {
println(m.changedAreas)
assertEquals("Bye friend", m.merged.str)
// assertTrue(m.noConflicts)
println(m.blocks)
}
@Test
fun testMergeWithConflicts() {