added mrged blocks and README.md
This commit is contained in:
parent
3f6a16224e
commit
2262c66b63
31
README.md
31
README.md
@ -2,6 +2,37 @@
|
|||||||
|
|
||||||
Tho tools to make smart merge of two versions of changed original data. See [merge3] function online docs.
|
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
|
## Acknowledgments
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("multiplatform") version "1.8.0"
|
kotlin("multiplatform") version "1.8.0"
|
||||||
|
`maven-publish`
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "net.sergeych"
|
group = "net.sergeych"
|
||||||
@ -57,4 +58,23 @@ kotlin {
|
|||||||
val nativeMain by getting
|
val nativeMain by getting
|
||||||
val nativeTest 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
1944
kotlin-js-store/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@ -11,14 +11,68 @@ import net.sergeych.mp_logger.debug
|
|||||||
import net.sergeych.sprintf.sprintf
|
import net.sergeych.sprintf.sprintf
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merge result. This version does not report (yet) conflicts, just merges it all.
|
* Merge result. This version does not report (yet) conflicts, just merges it all. See also [blocks] for
|
||||||
* @param merged the best merged resut. In the case of the conflict usually has both variants concatennated in place
|
* 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
|
* @param changedAreas ranges where data were altered. could be used to highlight changes
|
||||||
*/
|
*/
|
||||||
class MergeResult<T>(
|
class MergeResult<T>(
|
||||||
val merged: List<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.
|
* 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<IntRange>()
|
||||||
private val updates = mutableListOf<ClosedRange<Int>>()
|
|
||||||
|
|
||||||
private fun applyDelta(delta: AbstractDelta<T>) {
|
private fun applyDelta(delta: AbstractDelta<T>) {
|
||||||
when (delta) {
|
when (delta) {
|
||||||
@ -149,12 +202,12 @@ private class Merge3<T>(val source: List<T>, val variantA: List<T>, val variantB
|
|||||||
// detect ranges
|
// detect ranges
|
||||||
var conflictStart = -1
|
var conflictStart = -1
|
||||||
var changeStart = -1
|
var changeStart = -1
|
||||||
fun closeConflict(index: Int) {
|
// fun closeConflict(index: Int) {
|
||||||
if (conflictStart >= 0) {
|
// if (conflictStart >= 0) {
|
||||||
conflicts.add(conflictStart until index)
|
// conflicts.add(conflictStart until index)
|
||||||
conflictStart = -1
|
// conflictStart = -1
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
fun closeUpdate(index: Int) {
|
fun closeUpdate(index: Int) {
|
||||||
if (changeStart >= 0) {
|
if (changeStart >= 0) {
|
||||||
@ -167,13 +220,13 @@ private class Merge3<T>(val source: List<T>, val variantA: List<T>, val variantB
|
|||||||
when (count) {
|
when (count) {
|
||||||
0 -> {
|
0 -> {
|
||||||
// close conflict or update if was open
|
// close conflict or update if was open
|
||||||
closeConflict(index)
|
// closeConflict(index)
|
||||||
closeUpdate(index)
|
closeUpdate(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
1 -> {
|
1 -> {
|
||||||
// could be end of the conflict
|
// could be end of the conflict
|
||||||
closeConflict(index)
|
// closeConflict(index)
|
||||||
// open change if not opened
|
// open change if not opened
|
||||||
if (changeStart < 0) changeStart = index
|
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)
|
closeUpdate(changeCount.size)
|
||||||
|
|
||||||
return MergeResult<T>(result, updates)
|
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> =
|
fun <T> merge3(source: List<T>, a: List<T>, b: List<T>, showDebug: Boolean = false): MergeResult<T> =
|
||||||
Merge3(source, a, b).also {
|
Merge3(source, a, b).also {
|
||||||
if( showDebug ) it.logLevel = Log.Level.DEBUG
|
if (showDebug) it.logLevel = Log.Level.DEBUG
|
||||||
}.perform()
|
}.perform()
|
@ -32,6 +32,7 @@ class BasicTest {
|
|||||||
println(m.changedAreas)
|
println(m.changedAreas)
|
||||||
assertEquals("Bye friend", m.merged.str)
|
assertEquals("Bye friend", m.merged.str)
|
||||||
// assertTrue(m.noConflicts)
|
// assertTrue(m.noConflicts)
|
||||||
|
println(m.blocks)
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
fun testMergeWithConflicts() {
|
fun testMergeWithConflicts() {
|
Loading…
Reference in New Issue
Block a user