fix #1 fix #2 fix #3
fixed and enhanced merge
This commit is contained in:
Sergey Chernov 2024-03-20 00:08:20 +01:00
parent 6a67a4e840
commit 446a7adf6f
11 changed files with 1425 additions and 400 deletions

View File

@ -1,10 +1,10 @@
plugins { plugins {
kotlin("multiplatform") version "1.7.21" kotlin("multiplatform") version "1.9.22"
`maven-publish` `maven-publish`
} }
group = "net.sergeych" group = "net.sergeych"
version = "0.0.8-SNAPSHOT" version = "0.0.10"
repositories { repositories {
mavenCentral() mavenCentral()
@ -14,7 +14,7 @@ repositories {
kotlin { kotlin {
jvm { jvm {
jvmToolchain(8) jvmToolchain(11)
withJava() withJava()
testRuns["test"].executionTask.configure { testRuns["test"].executionTask.configure {
useJUnitPlatform() useJUnitPlatform()
@ -29,25 +29,63 @@ kotlin {
} }
} }
} }
val hostOs = System.getProperty("os.name") // val hostOs = System.getProperty("os.name")
val isMingwX64 = hostOs.startsWith("Windows") // val isMingwX64 = hostOs.startsWith("Windows")
val nativeTarget = when { // val nativeTarget = when {
hostOs == "Mac OS X" -> macosX64("native") // hostOs == "Mac OS X" -> macosX64("native")
hostOs == "Linux" -> linuxX64("native") // hostOs == "Linux" -> linuxX64("native")
isMingwX64 -> mingwX64("native") // isMingwX64 -> mingwX64("native")
else -> throw GradleException("Host OS is not supported in Kotlin/Native.") // else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
// }
wasmJs {
browser()
binaries.executable()
}
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach {
it.binaries.framework {
baseName = "morozilko_lib"
isStatic = true
}
}
listOf(
macosX64(),
macosArm64()
).forEach {
it.binaries.framework {
baseName = "morozilko_lib"
isStatic = true
}
}
linuxX64 {
binaries.staticLib {
baseName = "morozilko_lib"
}
}
mingwX64 {
binaries.staticLib {
baseName = "morozilko_lib"
}
} }
sourceSets { sourceSets {
val commonMain by getting { val commonMain by getting {
dependencies { dependencies {
// implementation("dev.gitlive:kotlin-diff-utils:4.1.4") // implementation("net.sergeych:mp_stools:[1.4.7,)")
} }
} }
val commonTest by getting { val commonTest by getting {
dependencies { dependencies {
implementation("net.sergeych:mp_stools:[1.3.4,)")
implementation(kotlin("test")) implementation(kotlin("test"))
} }
} }
@ -55,8 +93,8 @@ kotlin {
val jvmTest by getting val jvmTest by getting
val jsMain by getting val jsMain by getting
val jsTest by getting val jsTest by getting
val nativeMain by getting // val nativeMain by getting
val nativeTest by getting // val nativeTest by getting
} }
publishing { publishing {

File diff suppressed because it is too large Load Diff

View File

@ -44,7 +44,7 @@ class MeyersDiff<T> : DiffAlgorithmI<T> {
override fun computeDiff(source: List<T>, target: List<T>, progress: DiffAlgorithmListener?): List<Change> { override fun computeDiff(source: List<T>, target: List<T>, progress: DiffAlgorithmListener?): List<Change> {
progress?.diffStart() progress?.diffStart()
val path = buildPath(source, target, progress) val path = buildPath(source, target, progress)
val result = buildRevision(path, source, target) val result = buildRevision(path)
progress?.diffEnd() progress?.diffEnd()
return result return result
} }
@ -116,7 +116,7 @@ class MeyersDiff<T> : DiffAlgorithmI<T> {
* @throws DifferentiationFailedException if a [Patch] could not be * @throws DifferentiationFailedException if a [Patch] could not be
* built from the given path. * built from the given path.
*/ */
private fun buildRevision(actualPath: PathNode, orig: List<T>, rev: List<T>): List<Change> { private fun buildRevision(actualPath: PathNode): List<Change> {
var path: PathNode? = actualPath var path: PathNode? = actualPath
val changes: MutableList<Change> = ArrayList() val changes: MutableList<Change> = ArrayList()
if (path!!.isSnake) { if (path!!.isSnake) {

View File

@ -62,7 +62,7 @@ abstract class AbstractDelta<T>(val type: DeltaType, val source: Chunk<T>, val t
if (this::class != other::class) { if (this::class != other::class) {
return false return false
} }
val other = other as AbstractDelta<*> other as AbstractDelta<*>
if (source != other.source) { if (source != other.source) {
return false return false
} }
@ -70,4 +70,14 @@ abstract class AbstractDelta<T>(val type: DeltaType, val source: Chunk<T>, val t
false false
} else type == other.type } else type == other.type
} }
/**
* If this delta is partially performed by [existing], update it si it only changes necessary
* data. If this delta copies completely an existing delta, return null.
*
* Base class implementation only removes complete dupes
*/
open fun optimizeAgainst(existing: AbstractDelta<T>): AbstractDelta<T>? {
return if( this == existing ) null else this
}
} }

View File

@ -15,6 +15,8 @@
*/ */
package dev.gitlive.difflib.patch package dev.gitlive.difflib.patch
import net.sergeych.merge3.overlaps
/** /**
* Holds the information about the part of text involved in the diff process * Holds the information about the part of text involved in the diff process
* *
@ -28,7 +30,7 @@ package dev.gitlive.difflib.patch
* *
* *
* @author [](dm.naumenko@gmail.com>Dmitry Naumenko</a> * @author [](dm.naumenko@gmail.com>Dmitry Naumenko</a>
@param <T> The type of the compared elements in the 'lines'. * @param <T> The type of the compared elements in the 'lines'.
) */ ) */
class Chunk<T> { class Chunk<T> {
@ -130,8 +132,8 @@ class Chunk<T> {
if (this::class != other::class) { if (this::class != other::class) {
return false return false
} }
val other = other as Chunk<*>? other as Chunk<*>
if (lines != other!!.lines) { if (lines != other.lines) {
return false return false
} }
return position == other.position return position == other.position
@ -140,4 +142,11 @@ class Chunk<T> {
override fun toString(): String { override fun toString(): String {
return "[position: " + position + ", size: " + size() + ", lines: " + lines + "]" return "[position: " + position + ", size: " + size() + ", lines: " + lines + "]"
} }
/**
* Range that this chunk covers (start incusive, end non inclusive)
*/
fun range() = position..< position+size()
fun overlaps(target: Chunk<T>): Boolean = range() overlaps target.range()
} }

View File

@ -29,7 +29,7 @@ class InsertDelta<T>
* @param revised The original chunk. Must not be `null`. * @param revised The original chunk. Must not be `null`.
*/ */
(original: Chunk<T>, revised: Chunk<T>) : AbstractDelta<T>(DeltaType.INSERT, original, revised) { (original: Chunk<T>, revised: Chunk<T>) : AbstractDelta<T>(DeltaType.INSERT, original, revised) {
// @Throws(PatchFailedException::class) // @Throws(PatchFailedException::class)
protected override fun applyTo(target: MutableList<T>) { protected override fun applyTo(target: MutableList<T>) {
val position = source.position val position = source.position
val lines: List<T> = this.target.lines val lines: List<T> = this.target.lines
@ -54,4 +54,35 @@ class InsertDelta<T>
override fun withChunks(original: Chunk<T>, revised: Chunk<T>): AbstractDelta<T> { override fun withChunks(original: Chunk<T>, revised: Chunk<T>): AbstractDelta<T> {
return InsertDelta(original, revised) return InsertDelta(original, revised)
} }
/**
* Eliminates inserting the same text in the same positions
*/
override fun optimizeAgainst(existing: AbstractDelta<T>): AbstractDelta<T>? {
return super.optimizeAgainst(existing)?.let { x ->
if (existing !is InsertDelta)
x
else {
// this algorithm only eliminates deltas that have the common prefix:
if (existing.target.position != target.position)
return this
// remove common prefix:
var offset = 0
while (existing.target.lines[offset] == target.lines[offset]) {
offset++
if (offset >= target.lines.size || offset >= existing.target.lines.size) break
}
if (offset >= target.lines.size) {
// removed completely
return null
}
// removed a part:
val tail = target.lines.drop(offset)
return InsertDelta(
Chunk(source.position, source.lines.drop(offset), source.changePosition?.let { it + offset }),
Chunk(target.position + offset, tail, target.changePosition?.let { it + offset })
)
}
}
}
} }

View File

@ -56,7 +56,7 @@ class DiffRow(
if (this::class != other::class) { if (this::class != other::class) {
return false return false
} }
val other = other as DiffRow other as DiffRow
if (newLine != other.newLine) { if (newLine != other.newLine) {
return false return false
} }

View File

@ -12,84 +12,71 @@ import dev.gitlive.difflib.patch.Chunk
class UnifiedDiffReader internal constructor(lineReader: LineReader) { class UnifiedDiffReader internal constructor(lineReader: LineReader) {
private val nextLine: LineReader private val nextLine: LineReader
private val data = UnifiedDiff() private val data = UnifiedDiff()
private val DIFF_COMMAND: UnifiedDiffLine = UnifiedDiffLine(true, "^diff\\s") { match: MatchResult, line: String -> private val DIFF_COMMAND: UnifiedDiffLine = UnifiedDiffLine(true, "^diff\\s") { _, line: String ->
processDiff( processDiff(
match,
line line
) )
} }
private val SIMILARITY_INDEX: UnifiedDiffLine = UnifiedDiffLine(true, "^similarity index (\\d+)%$") { match: MatchResult, line: String -> private val SIMILARITY_INDEX: UnifiedDiffLine = UnifiedDiffLine(true, "^similarity index (\\d+)%$") { match: MatchResult, _ ->
processSimilarityIndex( processSimilarityIndex(
match, match
line
) )
} }
private val INDEX: UnifiedDiffLine = UnifiedDiffLine(true, "^index\\s[\\da-zA-Z]+\\.\\.[\\da-zA-Z]+(\\s(\\d+))?$") { match: MatchResult, line: String -> private val INDEX: UnifiedDiffLine = UnifiedDiffLine(true, "^index\\s[\\da-zA-Z]+\\.\\.[\\da-zA-Z]+(\\s(\\d+))?$") { _: MatchResult, line: String ->
processIndex( processIndex(
match,
line line
) )
} }
private val FROM_FILE: UnifiedDiffLine = UnifiedDiffLine(true, "^---\\s") { match: MatchResult, line: String -> private val FROM_FILE: UnifiedDiffLine = UnifiedDiffLine(true, "^---\\s") { _, line: String ->
processFromFile( processFromFile(
match,
line line
) )
} }
private val TO_FILE: UnifiedDiffLine = UnifiedDiffLine(true, "^\\+\\+\\+\\s") { match: MatchResult, line: String -> private val TO_FILE: UnifiedDiffLine = UnifiedDiffLine(true, "^\\+\\+\\+\\s") { _, line: String ->
processToFile( processToFile(
match,
line line
) )
} }
private val RENAME_FROM: UnifiedDiffLine = UnifiedDiffLine(true, "^rename\\sfrom\\s(.+)$") { match: MatchResult, line: String -> private val RENAME_FROM: UnifiedDiffLine = UnifiedDiffLine(true, "^rename\\sfrom\\s(.+)$") { match: MatchResult, _: String ->
processRenameFrom( processRenameFrom(
match, match
line
) )
} }
private val RENAME_TO: UnifiedDiffLine = UnifiedDiffLine(true, "^rename\\sto\\s(.+)$") { match: MatchResult, line: String -> private val RENAME_TO: UnifiedDiffLine = UnifiedDiffLine(true, "^rename\\sto\\s(.+)$") { match: MatchResult, _: String ->
processRenameTo( processRenameTo(
match, match
line
) )
} }
private val NEW_FILE_MODE: UnifiedDiffLine = UnifiedDiffLine(true, "^new\\sfile\\smode\\s(\\d+)") { match: MatchResult, line: String -> private val NEW_FILE_MODE: UnifiedDiffLine = UnifiedDiffLine(true, "^new\\sfile\\smode\\s(\\d+)") { match: MatchResult, _: String ->
processNewFileMode( processNewFileMode(
match, match
line
) )
} }
private val DELETED_FILE_MODE: UnifiedDiffLine = UnifiedDiffLine(true, "^deleted\\sfile\\smode\\s(\\d+)") { match: MatchResult, line: String -> private val DELETED_FILE_MODE: UnifiedDiffLine = UnifiedDiffLine(true, "^deleted\\sfile\\smode\\s(\\d+)") { match: MatchResult, _: String ->
processDeletedFileMode( processDeletedFileMode(
match, match
line
) )
} }
private val BINARY_FILE_CHANGED: UnifiedDiffLine = UnifiedDiffLine(true, "Binary files (.*) and (.*) differ") { match: MatchResult, line: String -> private val BINARY_FILE_CHANGED: UnifiedDiffLine = UnifiedDiffLine(true, "Binary files (.*) and (.*) differ") { _, _ ->
processBinaryFileChange(match, line) processBinaryFileChange()
} }
private val CHUNK: UnifiedDiffLine = UnifiedDiffLine(false, UNIFIED_DIFF_CHUNK_REGEXP) { match: MatchResult, chunkStart: String -> private val CHUNK: UnifiedDiffLine = UnifiedDiffLine(false, UNIFIED_DIFF_CHUNK_REGEXP) { match: MatchResult, _: String ->
processChunk( processChunk(
match, match
chunkStart
) )
} }
private val LINE_NORMAL = UnifiedDiffLine("^\\s") { match: MatchResult, line: String -> private val LINE_NORMAL = UnifiedDiffLine("^\\s") { _: MatchResult, line: String ->
processNormalLine( processNormalLine(
match,
line line
) )
} }
private val LINE_DEL = UnifiedDiffLine("^-") { match: MatchResult, line: String -> private val LINE_DEL = UnifiedDiffLine("^-") { _: MatchResult, line: String ->
processDelLine( processDelLine(
match,
line line
) )
} }
private val LINE_ADD = UnifiedDiffLine("^\\+") { match: MatchResult, line: String -> private val LINE_ADD = UnifiedDiffLine("^\\+") { _: MatchResult, line: String ->
processAddLine( processAddLine(
match,
line line
) )
} }
@ -238,14 +225,14 @@ class UnifiedDiffReader internal constructor(lineReader: LineReader) {
} }
} }
private fun processDiff(match: MatchResult, line: String) { private fun processDiff(line: String) {
val fromTo = parseFileNames(line) val fromTo = parseFileNames(line)
actualFile!!.fromFile = fromTo[0] actualFile!!.fromFile = fromTo[0]
actualFile!!.toFile = fromTo[1] actualFile!!.toFile = fromTo[1]
actualFile!!.diffCommand = line actualFile!!.diffCommand = line
} }
private fun processSimilarityIndex(match: MatchResult, line: String) { private fun processSimilarityIndex(match: MatchResult) {
actualFile!!.similarityIndex = match.groupValues[1].toInt() actualFile!!.similarityIndex = match.groupValues[1].toInt()
} }
@ -285,7 +272,7 @@ class UnifiedDiffReader internal constructor(lineReader: LineReader) {
} }
} }
private fun processNormalLine(match: MatchResult, line: String) { private fun processNormalLine(line: String) {
val cline = line.substring(1) val cline = line.substring(1)
originalTxt.add(cline) originalTxt.add(cline)
revisedTxt.add(cline) revisedTxt.add(cline)
@ -293,7 +280,7 @@ class UnifiedDiffReader internal constructor(lineReader: LineReader) {
addLineIdx++ addLineIdx++
} }
private fun processAddLine(match: MatchResult, line: String) { private fun processAddLine(line: String) {
val cline = line.substring(1) val cline = line.substring(1)
revisedTxt.add(cline) revisedTxt.add(cline)
addLineIdx++ addLineIdx++
@ -301,7 +288,7 @@ class UnifiedDiffReader internal constructor(lineReader: LineReader) {
addLineIdxList.add(new_ln - 1 + addLineIdx) addLineIdxList.add(new_ln - 1 + addLineIdx)
} }
private fun processDelLine(match: MatchResult, line: String) { private fun processDelLine(line: String) {
val cline = line.substring(1) val cline = line.substring(1)
originalTxt.add(cline) originalTxt.add(cline)
delLineIdx++ delLineIdx++
@ -309,7 +296,7 @@ class UnifiedDiffReader internal constructor(lineReader: LineReader) {
delLineIdxList.add(old_ln - 1 + delLineIdx) delLineIdxList.add(old_ln - 1 + delLineIdx)
} }
private fun processChunk(match: MatchResult, chunkStart: String) { private fun processChunk(match: MatchResult) {
// finalizeChunk(); // finalizeChunk();
old_ln = toInteger(match, 1, 1) old_ln = toInteger(match, 1, 1)
old_size = toInteger(match, 2, 1) old_size = toInteger(match, 2, 1)
@ -323,37 +310,37 @@ class UnifiedDiffReader internal constructor(lineReader: LineReader) {
} }
} }
private fun processIndex(match: MatchResult, line: String) { private fun processIndex(line: String) {
actualFile!!.index = line.substring(6) actualFile!!.index = line.substring(6)
} }
private fun processFromFile(match: MatchResult, line: String) { private fun processFromFile(line: String) {
actualFile!!.fromFile = extractFileName(line) actualFile!!.fromFile = extractFileName(line)
actualFile!!.fromTimestamp = extractTimestamp(line) actualFile!!.fromTimestamp = extractTimestamp(line)
} }
private fun processToFile(match: MatchResult, line: String) { private fun processToFile(line: String) {
actualFile!!.toFile = extractFileName(line) actualFile!!.toFile = extractFileName(line)
actualFile!!.toTimestamp = extractTimestamp(line) actualFile!!.toTimestamp = extractTimestamp(line)
} }
private fun processRenameFrom(match: MatchResult, line: String) { private fun processRenameFrom(match: MatchResult) {
actualFile!!.renameFrom = match.groupValues[1] actualFile!!.renameFrom = match.groupValues[1]
} }
private fun processRenameTo(match: MatchResult, line: String) { private fun processRenameTo(match: MatchResult) {
actualFile!!.renameTo = match.groupValues[1] actualFile!!.renameTo = match.groupValues[1]
} }
private fun processNewFileMode(match: MatchResult, line: String) { private fun processNewFileMode(match: MatchResult) {
actualFile!!.newFileMode = match.groupValues[1] actualFile!!.newFileMode = match.groupValues[1]
} }
private fun processDeletedFileMode(match: MatchResult, line: String) { private fun processDeletedFileMode(match: MatchResult) {
actualFile!!.deletedFileMode = match.groupValues[1] actualFile!!.deletedFileMode = match.groupValues[1]
} }
private fun processBinaryFileChange(match: MatchResult, line: String) { private fun processBinaryFileChange() {
// Nothing happens yet // Nothing happens yet
} }

View File

@ -105,7 +105,6 @@ private class Merge3<T>(
} }
// private fun trace() { // private fun trace() {
//
// debug { result.indices.joinToString("") { "%3d".sprintf(it) } } // debug { result.indices.joinToString("") { "%3d".sprintf(it) } }
// debug { result.joinToString("") { "%3s".sprintf(it.toString()) } } // debug { result.joinToString("") { "%3s".sprintf(it.toString()) } }
// debug { // debug {
@ -205,22 +204,25 @@ private class Merge3<T>(
fun perform(): MergeResult<T> { fun perform(): MergeResult<T> {
val dA = diff(source, variantA).getDeltas() val dA = diff(source, variantA).getDeltas()
val dB = diff(source, variantB).getDeltas() val dB = removeDupes(diff(source, variantB).getDeltas(), dA)
debug { "dA: $dA" } debug { "dA: $dA" }
debug { "dB: $dB" } debug { "dB: $dB" }
// trace() // trace()
debug { "adding first set" } // optimized nust be applied first
for (d in dA) {
debug { "adding dA $d" }
applyDelta(d)
}
for (d in dB) { for (d in dB) {
debug { "adding dB $d" } debug { "adding dB $d" }
applyDelta(d) applyDelta(d)
} }
// then apply full
for (d in dA) {
debug { "adding dA $d" }
applyDelta(d)
}
// detect ranges // detect ranges
var conflictStart = -1 var conflictStart = -1
@ -272,6 +274,25 @@ private class Merge3<T>(
return MergeResult<T>(result, updates, reindex) return MergeResult<T>(result, updates, reindex)
} }
/**
* Remove deltas from [deltas] that are already included into [existing]. It might require
* modifying existing deltas or removing it completely
*/
private fun removeDupes(
deltas: List<AbstractDelta<T>>,
existing: List<AbstractDelta<T>>,
): List<AbstractDelta<T>> {
// partial solutions: complete removal
return deltas.filter { it !in existing }.mapNotNull { s0 ->
var s: AbstractDelta<T>? = s0
for (x in existing) {
s = s?.optimizeAgainst(x)
if( s == null ) break
}
s
}
}
} }
/** /**

View File

@ -0,0 +1,18 @@
package net.sergeych.merge3
/**
* Check that other is inside this (inclusive)
*/
operator fun <T: Comparable<T>>ClosedRange<T>.contains(other: ClosedRange<T>): Boolean =
other.start >= start && other.endInclusive <= endInclusive
/**
* Check that [other] overlaps (e.g., other has non-empty intersection with this), this also
* includes the case when other is inside this or this is inside other completely. Use [contains] to exclude
* the containing.
*/
infix fun <T: Comparable<T>>ClosedRange<T>.overlaps(other: ClosedRange<T>): Boolean {
return start in other || endInclusive in other
}

View File

@ -1,23 +1,23 @@
import net.sergeych.merge3.MergeResult import net.sergeych.merge3.MergeResult
import net.sergeych.merge3.MergedBlock import net.sergeych.merge3.MergedBlock
import net.sergeych.merge3.merge3 import net.sergeych.merge3.merge3
import net.sergeych.mp_logger.Log //import net.sergeych.mp_logger.Log
import net.sergeych.sprintf.sprintf //import net.sergeych.sprintf.sprintf
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue import kotlin.test.assertTrue
val List<Char>.str: String get() = joinToString("") val List<Char>.str: String get() = joinToString("")
fun <T>MergeResult<T>.toTrace(source: List<T>): String { // fun <T>MergeResult<T>.toTrace(source: List<T>): String {
val r = StringBuilder() // val r = StringBuilder()
r.append(merged.indices.joinToString("") { "%3d".sprintf(it) } + "\n") // r.append(merged.indices.joinToString("") { "%3d".sprintf(it) } + "\n")
r.append(merged.joinToString("") { "%3s".sprintf(it) } + "\n") // r.append(merged.joinToString("") { "%3s".sprintf(it) } + "\n")
r.append("---- source ----\n") // r.append("---- source ----\n")
r.append(source.joinToString("") { "%3s".sprintf(it) } + "\n") // r.append(source.joinToString("") { "%3s".sprintf(it) } + "\n")
r.append(sourceIndices.joinToString("") { "%3s".sprintf(it) } + "\n") // r.append(sourceIndices.joinToString("") { "%3s".sprintf(it) } + "\n")
return r.toString() // return r.toString()
} // }
class BasicTest { class BasicTest {
@Test @Test
@ -27,25 +27,25 @@ class BasicTest {
val b = "Bye world!!".toList() val b = "Bye world!!".toList()
val m = merge3(src, b, a) val m = merge3(src, b, a)
println(m.merged.str) // println(m.merged.str)
// println(m.conflicts) // println(m.conflicts)
println(m.changedAreas) // println(m.changedAreas)
assertEquals("Bye cruel world!!!", m.merged.str) assertEquals("Bye cruel world!!!", m.merged.str)
// assertTrue(m.noConflicts) // assertTrue(m.noConflicts)
} }
@Test @Test
fun testMergeNoConflicts3() { fun testMergeNoConflicts3() {
Log.connectConsole() // Log.connectConsole()
val src = "Hello, world!".toList() val src = "Hello, world!".toList()
val a = "Hello, friend!".toList() val a = "Hello, friend!".toList()
val b = "Bye world!".toList() val b = "Bye world!".toList()
val m = merge3(src, a, b, true) val m = merge3(src, a, b)
println(m.merged.str) // println(m.merged.str)
// println(m.conflicts) // println(m.conflicts)
println(m.changedAreas) // println(m.changedAreas)
assertEquals("Bye friend!", m.merged.str) assertEquals("Bye friend!", m.merged.str)
println(m.toTrace(src)) // println(m.toTrace(src))
// assertTrue(m.noConflicts) // assertTrue(m.noConflicts)
} }
@ -56,103 +56,142 @@ class BasicTest {
val b = "Bye world".toList() val b = "Bye world".toList()
val m = merge3(src, b, a) val m = merge3(src, b, a)
println(m.merged.str) // println(m.merged.str)
// println(m.conflicts) // println(m.conflicts)
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) // println(m.blocks)
} }
@Test @Test
fun testMergeWithConflicts() { fun testMergeWithConflicts() {
Log.connectConsole() // Log.connectConsole()
val src = "Hello world".toList() val src = "Hello world".toList()
val a = "Hello 123".toList() val a = "Hello 123".toList()
val b = "Hello 456".toList() val b = "Hello 456".toList()
val m = merge3(src, b, a, false) val m = merge3(src, b, a)
println(m.merged.str) // println(m.merged.str)
// println(m.conflicts) // println(m.conflicts)
println(m.changedAreas) // println(m.changedAreas)
assertEquals("Hello 123456", m.merged.str) assertEquals("Hello 456123", m.merged.str)
} }
@Test @Test
fun testMergeIndexes() { fun testMergeIndexes() {
Log.connectConsole() // Log.connectConsole()
val src = "Hello".toList() val src = "Hello".toList()
val a = "123 Hello".toList() val a = "123 Hello".toList()
val b = "456 Hello".toList() val b = "456 Hello".toList()
val m = merge3(src, b, a, true) val m = merge3(src, b, a)
// println(m.toTrace(src)) // println(m.toTrace(src))
// println(m.changedAreas) // println(m.changedAreas)
// for( b in m.blocks ) // for( b in m.blocks )
// println(b) // println(b)
val unchanged = m.blocks.last() as MergedBlock.Unchanged val unchanged = m.blocks.last() as MergedBlock.Unchanged
println(unchanged) // println(unchanged)
assertEquals(0, unchanged.referenceIndex) assertEquals(0, unchanged.referenceIndex)
} }
@Test @Test
fun testMergeIndexes2() { fun testMergeIndexes2() {
Log.connectConsole() // Log.connectConsole()
val src = "Hello".toList() val src = "Hello".toList()
val a = "1Hello".toList() val a = "1Hello".toList()
val b = "2Hello".toList() val b = "2Hello".toList()
val m = merge3(src, b, a, true) val m = merge3(src, b, a)
val unchanged = m.blocks.last() as MergedBlock.Unchanged val unchanged = m.blocks.last() as MergedBlock.Unchanged
println(unchanged) // println(unchanged)
assertEquals(0, unchanged.referenceIndex) assertEquals(0, unchanged.referenceIndex)
} }
@Test @Test
fun testMergeFilter() { fun testMergeFilter() {
Log.connectConsole() // Log.connectConsole()
val source = "lxcvv".toList() val source = "lxcvv".toList()
val our = "lssdfasdwerwev".toList() val our = "lssdfasdwerwev".toList()
val their = "lxcvasdfasdfs".toList() val their = "lxcvasdfasdfs".toList()
val result = merge3(source, their, our) val result = merge3(source, their, our)
println("got result: ${result.toTrace(source)}") // println("got result: ${result.toTrace(source)}")
println("got result: ${result.blocks}") // println("got result: ${result.blocks}")
result.blocks.filter { it is MergedBlock.Unchanged } result.blocks.filter { it is MergedBlock.Unchanged }
assertTrue(true) assertTrue(true)
} }
@Test @Test
fun testMergeEmpty() { fun testMergeEmpty() {
Log.connectConsole() // Log.connectConsole()
val source = "".toList() val source = "".toList()
val our = "".toList() val our = "".toList()
val their = "".toList() val their = "".toList()
val result = merge3(source, their, our) val result = merge3(source, their, our)
println("got result: ${result.toTrace(source)}") // println("got result: ${result.toTrace(source)}")
println("got result: ${result.blocks}") // println("got result: ${result.blocks}")
result.blocks.filter { it is MergedBlock.Unchanged } result.blocks.filter { it is MergedBlock.Unchanged }
assertTrue(true) assertTrue(true)
} }
@Test @Test
fun testMergeUnchanged() { fun testMergeUnchanged1() {
Log.connectConsole()
val source = "abc".toList() val source = "abc".toList()
val their = "abDEF".toList() val their = "abcDEF".toList()
val our = "abcGHY".toList() val our = "abcDEF".toList()
val result = merge3(source, their, our) val result = merge3(source, their, our)
println(result.merged.str) // println(result.merged.str)
assertTrue(false) // println(result.blocks)
assertEquals("abcDEF", result.merged.str)
} }
@Test @Test
fun testMergeConflictOrder() { fun testMergePartial1() {
val source = "abc".toList()
val their = "abcDEF1".toList()
val our = "abcDEF".toList()
val result = merge3(source, their, our)
// println(result.merged.str)
// println(result.blocks)
assertEquals("abcDEF1", result.merged.str)
}
@Test
fun testMergePartial2() {
val source = "abc".toList()
val their = "abcDEF".toList()
val our = "abcDEF1".toList()
val result = merge3(source, their, our)
// println(result.merged.str)
// println(result.blocks)
assertEquals("abcDEF1", result.merged.str)
}
@Test
fun testMergeConflictOrder1() {
// Log.connectConsole()
val source = "abc".toList()
val their = "abcDEF".toList()
val our = "abcGHY".toList()
val result = merge3(source, their, our)
// println(result.merged.str)
// println(result.blocks)
// the order is unexpected:
assertTrue { result.merged.str in listOf("abcDEFGHY", "abcGHYDEF") }
}
@Test
fun testMergeConflictOrder2() {
val source = "ABAB".toList() val source = "ABAB".toList()
val our = "ACAC".toList() val our = "ACAC".toList()
val their = "ADAD".toList() val their = "ADAD".toList()
val result = merge3(source, their, our) val result = merge3(source, their, our)
println(result.merged.str) // println(result.merged.str)
assertTrue(false) // The order is undetermied:
assertTrue(result.merged.str in listOf("ACDADC", "ADCACD", "ADCADC", "ACDACD"))
} }
} }