parent
6a67a4e840
commit
446a7adf6f
@ -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
@ -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) {
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
@ -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()
|
||||||
}
|
}
|
@ -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 })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
18
src/commonMain/kotlin/net.sergeych.merge3/range_tools.kt
Normal file
18
src/commonMain/kotlin/net.sergeych.merge3/range_tools.kt
Normal 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
|
||||||
|
}
|
@ -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"))
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user