simplemerge: unify the interface of automerge algorithms

Summary:
This diff we unify the interface of automerge algorithms (trywordmerge,
merge_adjacent_changes):

```
def automerge_algo(base_lines, a_lines, b_lines) -> Optional[List[bytes]]:
    pass
```

Reviewed By: quark-zju

Differential Revision: D48845460

fbshipit-source-id: e018d1e6d6eb468d0a6eefed9905facb774055d6
This commit is contained in:
Zhaolong Zhu 2023-08-31 12:52:34 -07:00 committed by Facebook GitHub Bot
parent 812d0b2e6d
commit 667c59bd7f
2 changed files with 31 additions and 61 deletions

View File

@ -5,6 +5,7 @@
import time
from dataclasses import dataclass
from typing import List, Optional
from edenscm import commands, error, mdiff, registrar, scmutil
from edenscm.i18n import _
@ -16,20 +17,10 @@ command = registrar.command(cmdtable)
A, B, BASE = range(3)
class Conflict:
def __init__(self, base_lines, a_lines, b_lines):
self.base_lines = base_lines
self.a_lines = a_lines
self.b_lines = b_lines
self.merged_lines = []
def merge_adjacent_changes(c: Conflict) -> bool:
base_lines, a_lines, b_lines = c.base_lines, c.a_lines, c.b_lines
def merge_adjacent_changes(base_lines, a_lines, b_lines) -> Optional[List[bytes]]:
# require something to be changed
if not base_lines:
return False
return None
ablocks = unmatching_blocks(base_lines, a_lines)
bblocks = unmatching_blocks(base_lines, b_lines)
@ -40,11 +31,11 @@ def merge_adjacent_changes(c: Conflict) -> bool:
while indexes[A] < len(ablocks) and indexes[B] < len(bblocks):
ablock, bblock = ablocks[indexes[A]], bblocks[indexes[B]]
if is_overlap(ablock[0], ablock[1], bblock[0], bblock[1]):
return False
return None
elif is_non_unique_separator_for_insertion(
base_lines, a_lines, b_lines, ablock, bblock
):
return False
return None
i, block, lines = (
(A, ablock, a_lines) if ablock[0] < bblock[0] else (B, bblock, b_lines)
@ -73,9 +64,7 @@ def merge_adjacent_changes(c: Conflict) -> bool:
# add base lines at the end of block
merged_lines.extend(base_lines[k:])
c.merged_lines = merged_lines
return True
return merged_lines
class SmartMerge3Text(Merge3Text):
@ -85,18 +74,7 @@ class SmartMerge3Text(Merge3Text):
def __init__(self, basetext, atext, btext, wordmerge=wordmergemode.disabled):
Merge3Text.__init__(self, basetext, atext, btext, wordmerge=wordmerge)
def resolve_conflict(self, base_lines, a_lines, b_lines):
"""Try automerge algorithms to resolve the conflict region.
Return resolved lines, or None if auto resolution failed.
"""
c = Conflict(base_lines, a_lines, b_lines)
if merge_adjacent_changes(c):
return c.merged_lines
return None
self.automerge_fns.append(merge_adjacent_changes)
def is_non_unique_separator_for_insertion(

View File

@ -80,13 +80,17 @@ class wordmergemode:
return cls.disabled
def trywordmerge(basetext, atext, btext):
def trywordmerge(base_lines, a_lines, b_lines):
"""Try resolve conflicts using wordmerge.
Return resolved text, or None if merge failed.
Return resolved lines, or None if merge failed.
"""
basetext = b"".join(base_lines)
atext = b"".join(a_lines)
btext = b"".join(b_lines)
try:
m3 = Merge3Text(basetext, atext, btext, wordmerge=wordmergemode.enforced)
return b"".join(render_markers(m3)[0])
text = b"".join(render_markers(m3)[0])
return text.splitlines(True)
except CantShowWordConflicts:
return None
@ -130,16 +134,14 @@ class Merge3Text:
self.a = split(atext)
self.b = split(btext)
self.wordmerge = wordmerge
def resolve_conflict(self, base_lines, a_lines, b_lines):
"""Try automerge algorithms to resolve the conflict region.
Return resolved lines, or None if auto resolution failed.
"""
return None
self.automerge_fns = (
[trywordmerge] if wordmerge == wordmergemode.ondemand else []
)
def merge_groups(self, automerge=False):
"""Yield sequence of line groups. Each one is a tuple:
"""Yield sequence of line groups.
Each one is a tuple:
'unchanged', lines
Lines unchanged from base
@ -168,31 +170,21 @@ class Merge3Text:
elif what == "b":
yield what, self.b[t[1] : t[2]]
elif what == "conflict":
if self.wordmerge is wordmergemode.enforced:
raise CantShowWordConflicts()
base_lines = self.base[t[1] : t[2]]
a_lines = self.a[t[3] : t[4]]
b_lines = self.b[t[5] : t[6]]
merged_lines = None
if automerge:
if self.wordmerge is wordmergemode.enforced:
raise CantShowWordConflicts()
elif self.wordmerge is wordmergemode.ondemand:
# Try resolve the conflicted region using word merge
subbasetext = b"".join(base_lines)
subatext = b"".join(a_lines)
subbtext = b"".join(b_lines)
text = trywordmerge(subbasetext, subatext, subbtext)
if text:
merged_lines = text.splitlines(True)
else:
merged_lines = self.resolve_conflict(
base_lines, a_lines, b_lines
)
if merged_lines is not None:
yield "automerge", merged_lines
else:
yield what, (base_lines, a_lines, b_lines)
for fn in self.automerge_fns:
merged_lines = fn(base_lines, a_lines, b_lines)
if merged_lines is not None:
yield "automerge", merged_lines
break
if merged_lines is None:
yield (what, (base_lines, a_lines, b_lines))
else:
raise ValueError(what)
@ -430,8 +422,8 @@ def render_markers(
lines = []
for what, group_lines in merge_groups:
if what == "conflict":
base_lines, a_lines, b_lines = group_lines
conflictscount += 1
base_lines, a_lines, b_lines = group_lines
if start_marker is not None:
lines.append(start_marker + newline)
lines.extend(a_lines)