File: C:/Users/fred/anaconda3/Lib/site-packages/three_merge/merge.py
# -*- coding: utf-8 -*-
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
# (see LICENSE for details)
"""Main three_merge function."""
# Third-party imports
from diff_match_patch import diff_match_patch
# Constants
DIFFER = diff_match_patch()
PRESERVED = 0
DELETION = -1
ADDITION = 1
def merge(source: str, target: str, base: str) -> str:
diff1_l = DIFFER.diff_main(base, source)
diff2_l = DIFFER.diff_main(base, target)
diff1 = iter(diff1_l)
diff2 = iter(diff2_l)
composed_text = []
source = next(diff1, None)
target = next(diff2, None)
while source is not None and target is not None:
source_status, source_text = source
target_status, target_text = target
if source_status == PRESERVED and target_status == PRESERVED:
# Base is preserved for both source and target
if len(source_text) > len(target_text):
# Addition performed by target
advance = True
composed_text.append(target_text)
_, (_, invariant) = DIFFER.diff_main(target_text, source_text)
target = next(diff2, None)
while invariant != '' and target is not None:
# Apply target changes until invariant is preserved
# target = next(diff2, None)
target_status, target_text = target
if target_status == DELETION:
if len(target_text) > len(invariant):
target_text = target_text[len(invariant):]
invariant = ''
target = (target_status, target_text)
else:
invariant = invariant[len(target_text):]
target = next(diff2, None)
elif target_status == ADDITION:
composed_text.append(target_text)
target = next(diff2, None)
else:
# Recompute invariant and advance source
if len(invariant) > len(target_text):
assert invariant[:len(target_text)] == target_text
source = (
source_status, invariant[len(target_text):])
composed_text.append(target_text)
invariant = ''
advance = False
target = next(diff2, None)
else:
target_text = target_text[len(invariant):]
composed_text.append(invariant)
invariant = ''
target = (target_status, target_text)
if advance:
source = next(diff1, None)
elif len(source_text) < len(target_text):
# Addition performed by source
advance = True
composed_text.append(source_text)
_, (_, invariant) = DIFFER.diff_main(source_text, target_text)
source = next(diff1, None)
while invariant != '' and target is not None:
# Apply source changes until invariant is preserved
source_status, source_text = source
if source_status == DELETION:
if len(source_text) > len(invariant):
source_text = source_text[len(invariant):]
invariant = ''
source = (source_status, source_text)
else:
invariant = invariant[len(source_text):]
source = next(diff1, None)
elif source_status == ADDITION:
composed_text.append(source_text)
source = next(diff1, None)
else:
# Recompute invariant and advance source
# invariant = invariant[:len(source_text)]
if len(invariant) > len(source_text):
assert invariant[:len(source_text)] == source_text
target = (
target_status, invariant[len(source_text):])
composed_text.append(source_text)
invariant = ''
advance = False
source = next(diff1, None)
else:
source_text = source_text[len(invariant):]
composed_text.append(invariant)
invariant = ''
source = (source_status, source_text)
if advance:
target = next(diff2, None)
else:
# Source and target are equal
composed_text.append(source_text)
source = next(diff1, None)
target = next(diff2, None)
elif source_status == ADDITION and target_status == PRESERVED:
# Source is adding text
composed_text.append(source_text)
source = next(diff1, None)
elif source_status == PRESERVED and target_status == ADDITION:
# Target is adding text
composed_text.append(target_text)
target = next(diff2, None)
elif source_status == DELETION and target_status == PRESERVED:
if len(target_text) > len(source_text):
# Take target text, remove the corresponding part from source
target_text = target_text[len(source_text):]
# composed_text.append(target_text)
# source = diff1.pop(0)
target = (target_status, target_text)
source = next(diff1, None)
elif len(target_text) < len(source_text):
source_text = source_text[len(target_text):]
source = (source_status, source_text)
target = next(diff2, None)
elif source_status == PRESERVED and target_status == DELETION:
if len(source_text) > len(target_text):
# Take source text, remove the corresponding part from target
source_text = source_text[len(target_text):]
source = (source_status, source_text)
target = next(diff2, None)
elif len(source_text) < len(target_text):
# Advance to next source
target_text = target_text[len(source_text):]
target = (target_status, target_text)
source = next(diff1, None)
elif source_status == DELETION and target_status == ADDITION:
# Merge conflict
composed_text.append('<<<<<<< ++ {0} '.format(target_text))
composed_text.append('======= -- {0} '.format(source_text))
composed_text.append('>>>>>>>')
source = next(diff1, None)
target = next(diff2, None)
if target is not None:
target_status, target_text = target
if target_text.startswith(source_text):
target_text = target_text[len(source_text):]
target = (target_status, target_text)
elif source_status == ADDITION and target_status == DELETION:
# Merge conflict
composed_text.append('<<<<<<< ++ {0} '.format(source_text))
composed_text.append('======= -- {0} '.format(target_text))
composed_text.append('>>>>>>>')
source = next(diff1, None)
target = next(diff2, None)
if source is not None:
source_status, source_text = source
if source_text.startswith(target_text):
source_text = source_text[len(target_text):]
source = (source_status, source_text)
elif source_status == ADDITION and target_status == ADDITION:
# Possible merge conflict
if len(source_text) >= len(target_text):
if source_text.startswith(target_text):
composed_text.append(source_text)
else:
# Merge conflict
composed_text.append('<<<<<<< ++ {0} '.format(source_text))
composed_text.append('======= ++ {0} '.format(target_text))
composed_text.append('>>>>>>>')
else:
if target_text.startswith(source_text):
composed_text.append(target_text)
else:
# Merge conflict
composed_text.append('<<<<<<< ++ {0} '.format(source_text))
composed_text.append('======= ++ {0} '.format(target_text))
composed_text.append('>>>>>>>')
source = next(diff1, None)
target = next(diff2, None)
elif source_status == DELETION and target_status == DELETION:
# Possible merge conflict
merge_conflict = False
if len(source_text) > len(target_text):
if source_text.startswith(target_text):
# Peek target to delete preserved text
source_text = source_text[len(target_text):]
source = (source_status, source_text)
target = next(diff2, None)
else:
merge_conflict = True
elif len(target_text) > len(source_text):
if target_text.startswith(source_text):
target_text = target_text[len(source_text):]
target = (target_status, target_text)
source = next(diff1, None)
else:
merge_conflict = True
else:
if target_text == source_text:
# Both source and target remove the same text
source = next(diff1, None)
target = next(diff2, None)
else:
merge_conflict = True
if merge_conflict:
composed_text.append('<<<<<<< -- {0} '.format(source_text))
composed_text.append('======= -- {0} '.format(target_text))
composed_text.append('>>>>>>>')
while source is not None:
source_status, source_text = source
assert source_status == ADDITION or source_status == PRESERVED
if source_status == ADDITION:
composed_text.append(source_text)
source = next(diff1, None)
while target is not None:
target_status, target_text = target
assert target_status == ADDITION or source_status == PRESERVED
if target_status == ADDITION:
composed_text.append(target_text)
target = next(diff2, None)
return ''.join(composed_text)