1
# Copyright (C) 2005-2011, 2016 Canonical Ltd
1
# Copyright (C) 2004, 2005 by Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22
from ..errors import BinaryFile
23
from ..sixish import (
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
from bzrlib.selftest import TestCaseInTempDir, TestCase
19
from bzrlib.merge3 import Merge3
20
from bzrlib.errors import CantReprocessAndShowBase
29
22
def split_lines(t):
30
return BytesIO(t).readlines()
23
from cStringIO import StringIO
24
return StringIO(t).readlines()
33
26
############################################################
34
27
# test case data from the gnu diffutils manual
36
TZU = split_lines(b""" The Nameless is the origin of Heaven and Earth;
29
TZU = split_lines(""" The Nameless is the origin of Heaven and Earth;
37
30
The named is the mother of all things.
39
32
Therefore let there always be non-being,
40
33
so we may see their subtlety,
41
34
And let there always be being,
92
85
they have different names.
96
89
-- The Way of Lao-Tzu, tr. Wing-tsit Chan
102
class TestMerge3(tests.TestCase):
94
class TestMerge3(TestCase):
104
96
def test_no_changes(self):
105
97
"""No conflicts because nothing changed"""
106
m3 = merge3.Merge3([b'aaa', b'bbb'],
110
self.assertEqual(m3.find_unconflicted(),
113
self.assertEqual(list(m3.find_sync_regions()),
119
self.assertEqual(list(m3.merge_regions()),
120
[('unchanged', 0, 2)])
122
self.assertEqual(list(m3.merge_groups()),
123
[('unchanged', [b'aaa', b'bbb'])])
98
m3 = Merge3(['aaa', 'bbb'],
102
self.assertEquals(m3.find_unconflicted(),
105
self.assertEquals(list(m3.find_sync_regions()),
111
self.assertEquals(list(m3.merge_regions()),
112
[('unchanged', 0, 2)])
114
self.assertEquals(list(m3.merge_groups()),
115
[('unchanged', ['aaa', 'bbb'])])
125
117
def test_front_insert(self):
126
m3 = merge3.Merge3([b'zz'],
127
[b'aaa', b'bbb', b'zz'],
119
['aaa', 'bbb', 'zz'],
130
122
# todo: should use a sentinal at end as from get_matching_blocks
131
123
# to match without zz
132
self.assertEqual(list(m3.find_sync_regions()),
134
(1, 1, 3, 3, 1, 1), ])
136
self.assertEqual(list(m3.merge_regions()),
138
('unchanged', 0, 1)])
140
self.assertEqual(list(m3.merge_groups()),
141
[('a', [b'aaa', b'bbb']),
142
('unchanged', [b'zz'])])
124
self.assertEquals(list(m3.find_sync_regions()),
128
self.assertEquals(list(m3.merge_regions()),
130
('unchanged', 0, 1)])
132
self.assertEquals(list(m3.merge_groups()),
133
[('a', ['aaa', 'bbb']),
134
('unchanged', ['zz'])])
144
136
def test_null_insert(self):
145
m3 = merge3.Merge3([],
148
140
# todo: should use a sentinal at end as from get_matching_blocks
149
141
# to match without zz
150
self.assertEqual(list(m3.find_sync_regions()),
151
[(0, 0, 2, 2, 0, 0)])
153
self.assertEqual(list(m3.merge_regions()),
156
self.assertEqual(list(m3.merge_lines()),
142
self.assertEquals(list(m3.find_sync_regions()),
145
self.assertEquals(list(m3.merge_regions()),
148
self.assertEquals(list(m3.merge_lines()),
159
151
def test_no_conflicts(self):
160
152
"""No conflicts because only one side changed"""
161
m3 = merge3.Merge3([b'aaa', b'bbb'],
162
[b'aaa', b'111', b'bbb'],
165
self.assertEqual(m3.find_unconflicted(),
168
self.assertEqual(list(m3.find_sync_regions()),
171
(2, 2, 3, 3, 2, 2), ])
173
self.assertEqual(list(m3.merge_regions()),
174
[('unchanged', 0, 1),
176
('unchanged', 1, 2), ])
153
m3 = Merge3(['aaa', 'bbb'],
154
['aaa', '111', 'bbb'],
157
self.assertEquals(m3.find_unconflicted(),
160
self.assertEquals(list(m3.find_sync_regions()),
165
self.assertEquals(list(m3.merge_regions()),
166
[('unchanged', 0, 1),
168
('unchanged', 1, 2),])
178
170
def test_append_a(self):
179
m3 = merge3.Merge3([b'aaa\n', b'bbb\n'],
180
[b'aaa\n', b'bbb\n', b'222\n'],
181
[b'aaa\n', b'bbb\n'])
171
m3 = Merge3(['aaa\n', 'bbb\n'],
172
['aaa\n', 'bbb\n', '222\n'],
183
self.assertEqual(b''.join(m3.merge_lines()),
175
self.assertEquals(''.join(m3.merge_lines()),
186
178
def test_append_b(self):
187
m3 = merge3.Merge3([b'aaa\n', b'bbb\n'],
188
[b'aaa\n', b'bbb\n'],
189
[b'aaa\n', b'bbb\n', b'222\n'])
179
m3 = Merge3(['aaa\n', 'bbb\n'],
181
['aaa\n', 'bbb\n', '222\n'])
191
self.assertEqual(b''.join(m3.merge_lines()),
183
self.assertEquals(''.join(m3.merge_lines()),
194
186
def test_append_agreement(self):
195
m3 = merge3.Merge3([b'aaa\n', b'bbb\n'],
196
[b'aaa\n', b'bbb\n', b'222\n'],
197
[b'aaa\n', b'bbb\n', b'222\n'])
187
m3 = Merge3(['aaa\n', 'bbb\n'],
188
['aaa\n', 'bbb\n', '222\n'],
189
['aaa\n', 'bbb\n', '222\n'])
199
self.assertEqual(b''.join(m3.merge_lines()),
191
self.assertEquals(''.join(m3.merge_lines()),
202
194
def test_append_clash(self):
203
m3 = merge3.Merge3([b'aaa\n', b'bbb\n'],
204
[b'aaa\n', b'bbb\n', b'222\n'],
205
[b'aaa\n', b'bbb\n', b'333\n'])
195
m3 = Merge3(['aaa\n', 'bbb\n'],
196
['aaa\n', 'bbb\n', '222\n'],
197
['aaa\n', 'bbb\n', '333\n'])
207
ml = m3.merge_lines(name_a=b'a',
212
self.assertEqual(b''.join(ml),
199
ml = m3.merge_lines(name_a='a',
204
self.assertEquals(''.join(ml),
223
215
def test_insert_agreement(self):
224
m3 = merge3.Merge3([b'aaa\n', b'bbb\n'],
225
[b'aaa\n', b'222\n', b'bbb\n'],
226
[b'aaa\n', b'222\n', b'bbb\n'])
216
m3 = Merge3(['aaa\n', 'bbb\n'],
217
['aaa\n', '222\n', 'bbb\n'],
218
['aaa\n', '222\n', 'bbb\n'])
228
ml = m3.merge_lines(name_a=b'a',
233
self.assertEqual(b''.join(ml), b'aaa\n222\nbbb\n')
220
ml = m3.merge_lines(name_a='a',
225
self.assertEquals(''.join(ml), 'aaa\n222\nbbb\n')
235
228
def test_insert_clash(self):
236
229
"""Both try to insert lines in the same place."""
237
m3 = merge3.Merge3([b'aaa\n', b'bbb\n'],
238
[b'aaa\n', b'111\n', b'bbb\n'],
239
[b'aaa\n', b'222\n', b'bbb\n'])
241
self.assertEqual(m3.find_unconflicted(),
244
self.assertEqual(list(m3.find_sync_regions()),
247
(2, 2, 3, 3, 3, 3), ])
249
self.assertEqual(list(m3.merge_regions()),
250
[('unchanged', 0, 1),
251
('conflict', 1, 1, 1, 2, 1, 2),
252
('unchanged', 1, 2)])
254
self.assertEqual(list(m3.merge_groups()),
255
[('unchanged', [b'aaa\n']),
256
('conflict', [], [b'111\n'], [b'222\n']),
257
('unchanged', [b'bbb\n']),
260
ml = m3.merge_lines(name_a=b'a',
265
self.assertEqual(b''.join(ml),
230
m3 = Merge3(['aaa\n', 'bbb\n'],
231
['aaa\n', '111\n', 'bbb\n'],
232
['aaa\n', '222\n', 'bbb\n'])
234
self.assertEquals(m3.find_unconflicted(),
237
self.assertEquals(list(m3.find_sync_regions()),
242
self.assertEquals(list(m3.merge_regions()),
244
('conflict', 1,1, 1,2, 1,2),
247
self.assertEquals(list(m3.merge_groups()),
248
[('unchanged', ['aaa\n']),
249
('conflict', [], ['111\n'], ['222\n']),
250
('unchanged', ['bbb\n']),
253
ml = m3.merge_lines(name_a='a',
258
self.assertEquals(''.join(ml),
275
268
def test_replace_clash(self):
276
269
"""Both try to insert lines in the same place."""
277
m3 = merge3.Merge3([b'aaa', b'000', b'bbb'],
278
[b'aaa', b'111', b'bbb'],
279
[b'aaa', b'222', b'bbb'])
281
self.assertEqual(m3.find_unconflicted(),
284
self.assertEqual(list(m3.find_sync_regions()),
287
(3, 3, 3, 3, 3, 3), ])
270
m3 = Merge3(['aaa', '000', 'bbb'],
271
['aaa', '111', 'bbb'],
272
['aaa', '222', 'bbb'])
274
self.assertEquals(m3.find_unconflicted(),
277
self.assertEquals(list(m3.find_sync_regions()),
289
282
def test_replace_multi(self):
290
283
"""Replacement with regions of different size."""
291
m3 = merge3.Merge3([b'aaa', b'000', b'000', b'bbb'],
292
[b'aaa', b'111', b'111', b'111', b'bbb'],
293
[b'aaa', b'222', b'222', b'222', b'222', b'bbb'])
295
self.assertEqual(m3.find_unconflicted(),
298
self.assertEqual(list(m3.find_sync_regions()),
301
(4, 4, 5, 5, 6, 6), ])
284
m3 = Merge3(['aaa', '000', '000', 'bbb'],
285
['aaa', '111', '111', '111', 'bbb'],
286
['aaa', '222', '222', '222', '222', 'bbb'])
288
self.assertEquals(m3.find_unconflicted(),
292
self.assertEquals(list(m3.find_sync_regions()),
303
297
def test_merge_poem(self):
304
298
"""Test case from diff3 manual"""
305
m3 = merge3.Merge3(TZU, LAO, TAO)
306
ml = list(m3.merge_lines(b'LAO', b'TAO'))
299
m3 = Merge3(TZU, LAO, TAO)
300
ml = list(m3.merge_lines('LAO', 'TAO'))
307
301
self.log('merge result:')
308
self.log(b''.join(ml))
309
self.assertEqual(ml, MERGED_RESULT)
302
self.log(''.join(ml))
303
self.assertEquals(ml, MERGED_RESULT)
311
def test_minimal_conflicts_common(self):
305
def test_minimal_conflicts(self):
312
306
"""Reprocessing"""
313
base_text = (b"a\n" * 20).splitlines(True)
314
this_text = (b"a\n" * 10 + b"b\n" * 10).splitlines(True)
315
other_text = (b"a\n" * 10 + b"c\n" + b"b\n" *
316
8 + b"c\n").splitlines(True)
317
m3 = merge3.Merge3(base_text, other_text, this_text)
318
m_lines = m3.merge_lines(b'OTHER', b'THIS', reprocess=True)
319
merged_text = b"".join(list(m_lines))
320
optimal_text = (b"a\n" * 10 + b"<<<<<<< OTHER\nc\n"
321
+ 8 * b"b\n" + b"c\n=======\n"
322
+ 10 * b"b\n" + b">>>>>>> THIS\n")
323
self.assertEqualDiff(optimal_text, merged_text)
325
def test_minimal_conflicts_unique(self):
327
"""Add a newline to each entry in the string"""
328
return [(int2byte(x) + b'\n') for x in bytearray(s)]
330
base_text = add_newline(b"abcdefghijklm")
331
this_text = add_newline(b"abcdefghijklmNOPQRSTUVWXYZ")
332
other_text = add_newline(b"abcdefghijklm1OPQRSTUVWXY2")
333
m3 = merge3.Merge3(base_text, other_text, this_text)
334
m_lines = m3.merge_lines(b'OTHER', b'THIS', reprocess=True)
335
merged_text = b"".join(list(m_lines))
336
optimal_text = b''.join(add_newline(b"abcdefghijklm")
337
+ [b"<<<<<<< OTHER\n1\n=======\nN\n>>>>>>> THIS\n"]
338
+ add_newline(b'OPQRSTUVWXY')
339
+ [b"<<<<<<< OTHER\n2\n=======\nZ\n>>>>>>> THIS\n"]
341
self.assertEqualDiff(optimal_text, merged_text)
343
def test_minimal_conflicts_nonunique(self):
345
"""Add a newline to each entry in the string"""
346
return [(int2byte(x) + b'\n') for x in bytearray(s)]
348
base_text = add_newline(b"abacddefgghij")
349
this_text = add_newline(b"abacddefgghijkalmontfprz")
350
other_text = add_newline(b"abacddefgghijknlmontfprd")
351
m3 = merge3.Merge3(base_text, other_text, this_text)
352
m_lines = m3.merge_lines(b'OTHER', b'THIS', reprocess=True)
353
merged_text = b"".join(list(m_lines))
354
optimal_text = b''.join(add_newline(b"abacddefgghijk")
355
+ [b"<<<<<<< OTHER\nn\n=======\na\n>>>>>>> THIS\n"]
356
+ add_newline(b'lmontfpr')
357
+ [b"<<<<<<< OTHER\nd\n=======\nz\n>>>>>>> THIS\n"]
359
self.assertEqualDiff(optimal_text, merged_text)
307
base_text = ("a\n" * 20).splitlines(True)
308
this_text = ("a\n"*10+"b\n" * 10).splitlines(True)
309
other_text = ("a\n"*10+"c\n"+"b\n" * 8 + "c\n").splitlines(True)
310
m3 = Merge3(base_text, other_text, this_text)
311
m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
312
merged_text = "".join(list(m_lines))
313
optimal_text = "a\n" * 10 + "<<<<<<< OTHER\nc\n=======\n>>>>>>> THIS"\
314
+ "\n" + 8* "b\n" + "<<<<<<< OTHER\nc\n=======\nb\nb\n>>>>>>>"\
316
self.assertEqualDiff(merged_text, optimal_text)
361
318
def test_reprocess_and_base(self):
362
319
"""Reprocessing and showing base breaks correctly"""
363
base_text = (b"a\n" * 20).splitlines(True)
364
this_text = (b"a\n" * 10 + b"b\n" * 10).splitlines(True)
365
other_text = (b"a\n" * 10 + b"c\n" + b"b\n" *
366
8 + b"c\n").splitlines(True)
367
m3 = merge3.Merge3(base_text, other_text, this_text)
368
m_lines = m3.merge_lines(b'OTHER', b'THIS', reprocess=True,
369
base_marker=b'|||||||')
370
self.assertRaises(merge3.CantReprocessAndShowBase, list, m_lines)
372
def test_binary(self):
373
self.assertRaises(BinaryFile, merge3.Merge3, [b'\x00'], [b'a'], [b'b'])
375
def test_dos_text(self):
378
other_text = b'c\r\n'
379
m3 = merge3.Merge3(base_text.splitlines(True),
380
other_text.splitlines(True),
381
this_text.splitlines(True))
382
m_lines = m3.merge_lines(b'OTHER', b'THIS')
383
self.assertEqual(b'<<<<<<< OTHER\r\nc\r\n=======\r\nb\r\n'
384
b'>>>>>>> THIS\r\n'.splitlines(True), list(m_lines))
386
def test_mac_text(self):
390
m3 = merge3.Merge3(base_text.splitlines(True),
391
other_text.splitlines(True),
392
this_text.splitlines(True))
393
m_lines = m3.merge_lines(b'OTHER', b'THIS')
394
self.assertEqual(b'<<<<<<< OTHER\rc\r=======\rb\r'
395
b'>>>>>>> THIS\r'.splitlines(True), list(m_lines))
397
def test_merge3_cherrypick(self):
398
base_text = b"a\nb\n"
400
other_text = b"a\nb\nc\n"
401
# When cherrypicking, lines in base are not part of the conflict
402
m3 = merge3.Merge3(base_text.splitlines(True),
403
this_text.splitlines(True),
404
other_text.splitlines(True), is_cherrypick=True)
405
m_lines = m3.merge_lines()
406
self.assertEqualDiff(b'a\n<<<<<<<\n=======\nc\n>>>>>>>\n',
409
# This is not symmetric
410
m3 = merge3.Merge3(base_text.splitlines(True),
411
other_text.splitlines(True),
412
this_text.splitlines(True), is_cherrypick=True)
413
m_lines = m3.merge_lines()
414
self.assertEqualDiff(b'a\n<<<<<<<\nb\nc\n=======\n>>>>>>>\n',
417
def test_merge3_cherrypick_w_mixed(self):
418
base_text = b'a\nb\nc\nd\ne\n'
419
this_text = b'a\nb\nq\n'
420
other_text = b'a\nb\nc\nd\nf\ne\ng\n'
421
# When cherrypicking, lines in base are not part of the conflict
422
m3 = merge3.Merge3(base_text.splitlines(True),
423
this_text.splitlines(True),
424
other_text.splitlines(True), is_cherrypick=True)
425
m_lines = m3.merge_lines()
426
self.assertEqualDiff(b'a\n'
439
def test_allow_objects(self):
440
"""Objects other than strs may be used with Merge3 when
443
merge_groups and merge_regions work with non-str input. Methods that
444
return lines like merge_lines fail.
446
base = [(x, x) for x in 'abcde']
447
a = [(x, x) for x in 'abcdef']
448
b = [(x, x) for x in 'Zabcde']
449
m3 = merge3.Merge3(base, a, b, allow_objects=True)
454
list(m3.merge_regions()))
456
[('b', [('Z', 'Z')]),
457
('unchanged', [(x, x) for x in 'abcde']),
458
('a', [('f', 'f')])],
459
list(m3.merge_groups()))
320
base_text = ("a\n" * 20).splitlines(True)
321
this_text = ("a\n"*10+"b\n" * 10).splitlines(True)
322
other_text = ("a\n"*10+"c\n"+"b\n" * 8 + "c\n").splitlines(True)
323
m3 = Merge3(base_text, other_text, this_text)
324
m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True,
325
base_marker='|||||||')
326
self.assertRaises(CantReprocessAndShowBase, list, m_lines)