816
818
def get_factory(self):
817
819
return KnitVersionedFile
822
class MergeCasesMixin(object):
824
def doMerge(self, base, a, b, mp):
825
from cStringIO import StringIO
826
from textwrap import dedent
832
w.add_lines('text0', [], map(addcrlf, base))
833
w.add_lines('text1', ['text0'], map(addcrlf, a))
834
w.add_lines('text2', ['text0'], map(addcrlf, b))
838
self.log('merge plan:')
839
p = list(w.plan_merge('text1', 'text2'))
840
for state, line in p:
842
self.log('%12s | %s' % (state, line[:-1]))
846
mt.writelines(w.weave_merge(p))
848
self.log(mt.getvalue())
850
mp = map(addcrlf, mp)
851
self.assertEqual(mt.readlines(), mp)
854
def testOneInsert(self):
860
def testSeparateInserts(self):
861
self.doMerge(['aaa', 'bbb', 'ccc'],
862
['aaa', 'xxx', 'bbb', 'ccc'],
863
['aaa', 'bbb', 'yyy', 'ccc'],
864
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
866
def testSameInsert(self):
867
self.doMerge(['aaa', 'bbb', 'ccc'],
868
['aaa', 'xxx', 'bbb', 'ccc'],
869
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
870
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
871
overlappedInsertExpected = ['aaa', 'xxx', 'yyy', 'bbb']
872
def testOverlappedInsert(self):
873
self.doMerge(['aaa', 'bbb'],
874
['aaa', 'xxx', 'yyy', 'bbb'],
875
['aaa', 'xxx', 'bbb'], self.overlappedInsertExpected)
877
# really it ought to reduce this to
878
# ['aaa', 'xxx', 'yyy', 'bbb']
881
def testClashReplace(self):
882
self.doMerge(['aaa'],
885
['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz',
888
def testNonClashInsert1(self):
889
self.doMerge(['aaa'],
892
['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz',
895
def testNonClashInsert2(self):
896
self.doMerge(['aaa'],
902
def testDeleteAndModify(self):
903
"""Clashing delete and modification.
905
If one side modifies a region and the other deletes it then
906
there should be a conflict with one side blank.
909
#######################################
910
# skippd, not working yet
913
self.doMerge(['aaa', 'bbb', 'ccc'],
914
['aaa', 'ddd', 'ccc'],
916
['<<<<<<<< ', 'aaa', '=======', '>>>>>>> ', 'ccc'])
918
def _test_merge_from_strings(self, base, a, b, expected):
920
w.add_lines('text0', [], base.splitlines(True))
921
w.add_lines('text1', ['text0'], a.splitlines(True))
922
w.add_lines('text2', ['text0'], b.splitlines(True))
923
self.log('merge plan:')
924
p = list(w.plan_merge('text1', 'text2'))
925
for state, line in p:
927
self.log('%12s | %s' % (state, line[:-1]))
928
self.log('merge result:')
929
result_text = ''.join(w.weave_merge(p))
930
self.log(result_text)
931
self.assertEqualDiff(result_text, expected)
933
def test_weave_merge_conflicts(self):
934
# does weave merge properly handle plans that end with unchanged?
935
result = ''.join(self.get_file().weave_merge([('new-a', 'hello\n')]))
936
self.assertEqual(result, 'hello\n')
938
def test_deletion_extended(self):
939
"""One side deletes, the other deletes more.
956
self._test_merge_from_strings(base, a, b, result)
958
def test_deletion_overlap(self):
959
"""Delete overlapping regions with no other conflict.
961
Arguably it'd be better to treat these as agreement, rather than
962
conflict, but for now conflict is safer.
990
self._test_merge_from_strings(base, a, b, result)
992
def test_agreement_deletion(self):
993
"""Agree to delete some lines, without conflicts."""
1015
self._test_merge_from_strings(base, a, b, result)
1017
def test_sync_on_deletion(self):
1018
"""Specific case of merge where we can synchronize incorrectly.
1020
A previous version of the weave merge concluded that the two versions
1021
agreed on deleting line 2, and this could be a synchronization point.
1022
Line 1 was then considered in isolation, and thought to be deleted on
1025
It's better to consider the whole thing as a disagreement region.
1036
a's replacement line 2
1049
a's replacement line 2
1056
self._test_merge_from_strings(base, a, b, result)
1059
class TestKnitMerge(TestCaseWithTransport, MergeCasesMixin):
1061
def get_file(self, name='foo'):
1062
return KnitVersionedFile(name, get_transport(self.get_url('.')),
1063
delta=True, create=True)
1065
def log_contents(self, w):
1069
class TestWeaveMerge(TestCaseWithTransport, MergeCasesMixin):
1071
def get_file(self, name='foo'):
1072
return WeaveFile(name, get_transport(self.get_url('.')), create=True)
1074
def log_contents(self, w):
1075
self.log('weave is:')
1077
write_weave(w, tmpf)
1078
self.log(tmpf.getvalue())
1080
overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======',
1081
'xxx', '>>>>>>> ', 'bbb']