##// END OF EJS Templates
simplemerge: delete unused find_unconflicted()...
Martin von Zweigbergk -
r47064:98e3a693 default
parent child Browse files
Show More
@@ -1,591 +1,566 b''
1 1 # Copyright (C) 2004, 2005 Canonical Ltd
2 2 #
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.
7 7 #
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.
12 12 #
13 13 # You should have received a copy of the GNU General Public License
14 14 # along with this program; if not, see <http://www.gnu.org/licenses/>.
15 15
16 16 # mbp: "you know that thing where cvs gives you conflict markers?"
17 17 # s: "i hate that."
18 18
19 19 from __future__ import absolute_import
20 20
21 21 from .i18n import _
22 22 from .node import nullid
23 23 from . import (
24 24 error,
25 25 mdiff,
26 26 pycompat,
27 27 util,
28 28 )
29 29 from .utils import stringutil
30 30
31 31
32 32 class CantReprocessAndShowBase(Exception):
33 33 pass
34 34
35 35
36 36 def intersect(ra, rb):
37 37 """Given two ranges return the range where they intersect or None.
38 38
39 39 >>> intersect((0, 10), (0, 6))
40 40 (0, 6)
41 41 >>> intersect((0, 10), (5, 15))
42 42 (5, 10)
43 43 >>> intersect((0, 10), (10, 15))
44 44 >>> intersect((0, 9), (10, 15))
45 45 >>> intersect((0, 9), (7, 15))
46 46 (7, 9)
47 47 """
48 48 assert ra[0] <= ra[1]
49 49 assert rb[0] <= rb[1]
50 50
51 51 sa = max(ra[0], rb[0])
52 52 sb = min(ra[1], rb[1])
53 53 if sa < sb:
54 54 return sa, sb
55 55 else:
56 56 return None
57 57
58 58
59 59 def compare_range(a, astart, aend, b, bstart, bend):
60 60 """Compare a[astart:aend] == b[bstart:bend], without slicing."""
61 61 if (aend - astart) != (bend - bstart):
62 62 return False
63 63 for ia, ib in zip(
64 64 pycompat.xrange(astart, aend), pycompat.xrange(bstart, bend)
65 65 ):
66 66 if a[ia] != b[ib]:
67 67 return False
68 68 else:
69 69 return True
70 70
71 71
72 72 class Merge3Text(object):
73 73 """3-way merge of texts.
74 74
75 75 Given strings BASE, OTHER, THIS, tries to produce a combined text
76 76 incorporating the changes from both BASE->OTHER and BASE->THIS."""
77 77
78 78 def __init__(self, basetext, atext, btext, base=None, a=None, b=None):
79 79 self.basetext = basetext
80 80 self.atext = atext
81 81 self.btext = btext
82 82 if base is None:
83 83 base = mdiff.splitnewlines(basetext)
84 84 if a is None:
85 85 a = mdiff.splitnewlines(atext)
86 86 if b is None:
87 87 b = mdiff.splitnewlines(btext)
88 88 self.base = base
89 89 self.a = a
90 90 self.b = b
91 91
92 92 def merge_lines(
93 93 self,
94 94 name_a=None,
95 95 name_b=None,
96 96 name_base=None,
97 97 start_marker=b'<<<<<<<',
98 98 mid_marker=b'=======',
99 99 end_marker=b'>>>>>>>',
100 100 base_marker=None,
101 101 localorother=None,
102 102 minimize=False,
103 103 ):
104 104 """Return merge in cvs-like form."""
105 105 self.conflicts = False
106 106 newline = b'\n'
107 107 if len(self.a) > 0:
108 108 if self.a[0].endswith(b'\r\n'):
109 109 newline = b'\r\n'
110 110 elif self.a[0].endswith(b'\r'):
111 111 newline = b'\r'
112 112 if name_a and start_marker:
113 113 start_marker = start_marker + b' ' + name_a
114 114 if name_b and end_marker:
115 115 end_marker = end_marker + b' ' + name_b
116 116 if name_base and base_marker:
117 117 base_marker = base_marker + b' ' + name_base
118 118 merge_regions = self.merge_regions()
119 119 if minimize:
120 120 merge_regions = self.minimize(merge_regions)
121 121 for t in merge_regions:
122 122 what = t[0]
123 123 if what == b'unchanged':
124 124 for i in range(t[1], t[2]):
125 125 yield self.base[i]
126 126 elif what == b'a' or what == b'same':
127 127 for i in range(t[1], t[2]):
128 128 yield self.a[i]
129 129 elif what == b'b':
130 130 for i in range(t[1], t[2]):
131 131 yield self.b[i]
132 132 elif what == b'conflict':
133 133 if localorother == b'local':
134 134 for i in range(t[3], t[4]):
135 135 yield self.a[i]
136 136 elif localorother == b'other':
137 137 for i in range(t[5], t[6]):
138 138 yield self.b[i]
139 139 else:
140 140 self.conflicts = True
141 141 if start_marker is not None:
142 142 yield start_marker + newline
143 143 for i in range(t[3], t[4]):
144 144 yield self.a[i]
145 145 if base_marker is not None:
146 146 yield base_marker + newline
147 147 for i in range(t[1], t[2]):
148 148 yield self.base[i]
149 149 if mid_marker is not None:
150 150 yield mid_marker + newline
151 151 for i in range(t[5], t[6]):
152 152 yield self.b[i]
153 153 if end_marker is not None:
154 154 yield end_marker + newline
155 155 else:
156 156 raise ValueError(what)
157 157
158 158 def merge_groups(self):
159 159 """Yield sequence of line groups. Each one is a tuple:
160 160
161 161 'unchanged', lines
162 162 Lines unchanged from base
163 163
164 164 'a', lines
165 165 Lines taken from a
166 166
167 167 'same', lines
168 168 Lines taken from a (and equal to b)
169 169
170 170 'b', lines
171 171 Lines taken from b
172 172
173 173 'conflict', base_lines, a_lines, b_lines
174 174 Lines from base were changed to either a or b and conflict.
175 175 """
176 176 for t in self.merge_regions():
177 177 what = t[0]
178 178 if what == b'unchanged':
179 179 yield what, self.base[t[1] : t[2]]
180 180 elif what == b'a' or what == b'same':
181 181 yield what, self.a[t[1] : t[2]]
182 182 elif what == b'b':
183 183 yield what, self.b[t[1] : t[2]]
184 184 elif what == b'conflict':
185 185 yield (
186 186 what,
187 187 self.base[t[1] : t[2]],
188 188 self.a[t[3] : t[4]],
189 189 self.b[t[5] : t[6]],
190 190 )
191 191 else:
192 192 raise ValueError(what)
193 193
194 194 def merge_regions(self):
195 195 """Return sequences of matching and conflicting regions.
196 196
197 197 This returns tuples, where the first value says what kind we
198 198 have:
199 199
200 200 'unchanged', start, end
201 201 Take a region of base[start:end]
202 202
203 203 'same', astart, aend
204 204 b and a are different from base but give the same result
205 205
206 206 'a', start, end
207 207 Non-clashing insertion from a[start:end]
208 208
209 209 'conflict', zstart, zend, astart, aend, bstart, bend
210 210 Conflict between a and b, with z as common ancestor
211 211
212 212 Method is as follows:
213 213
214 214 The two sequences align only on regions which match the base
215 215 and both descendants. These are found by doing a two-way diff
216 216 of each one against the base, and then finding the
217 217 intersections between those regions. These "sync regions"
218 218 are by definition unchanged in both and easily dealt with.
219 219
220 220 The regions in between can be in any of three cases:
221 221 conflicted, or changed on only one side.
222 222 """
223 223
224 224 # section a[0:ia] has been disposed of, etc
225 225 iz = ia = ib = 0
226 226
227 227 for region in self.find_sync_regions():
228 228 zmatch, zend, amatch, aend, bmatch, bend = region
229 229 # print 'match base [%d:%d]' % (zmatch, zend)
230 230
231 231 matchlen = zend - zmatch
232 232 assert matchlen >= 0
233 233 assert matchlen == (aend - amatch)
234 234 assert matchlen == (bend - bmatch)
235 235
236 236 len_a = amatch - ia
237 237 len_b = bmatch - ib
238 238 len_base = zmatch - iz
239 239 assert len_a >= 0
240 240 assert len_b >= 0
241 241 assert len_base >= 0
242 242
243 243 # print 'unmatched a=%d, b=%d' % (len_a, len_b)
244 244
245 245 if len_a or len_b:
246 246 # try to avoid actually slicing the lists
247 247 equal_a = compare_range(
248 248 self.a, ia, amatch, self.base, iz, zmatch
249 249 )
250 250 equal_b = compare_range(
251 251 self.b, ib, bmatch, self.base, iz, zmatch
252 252 )
253 253 same = compare_range(self.a, ia, amatch, self.b, ib, bmatch)
254 254
255 255 if same:
256 256 yield b'same', ia, amatch
257 257 elif equal_a and not equal_b:
258 258 yield b'b', ib, bmatch
259 259 elif equal_b and not equal_a:
260 260 yield b'a', ia, amatch
261 261 elif not equal_a and not equal_b:
262 262 yield b'conflict', iz, zmatch, ia, amatch, ib, bmatch
263 263 else:
264 264 raise AssertionError(b"can't handle a=b=base but unmatched")
265 265
266 266 ia = amatch
267 267 ib = bmatch
268 268 iz = zmatch
269 269
270 270 # if the same part of the base was deleted on both sides
271 271 # that's OK, we can just skip it.
272 272
273 273 if matchlen > 0:
274 274 assert ia == amatch
275 275 assert ib == bmatch
276 276 assert iz == zmatch
277 277
278 278 yield b'unchanged', zmatch, zend
279 279 iz = zend
280 280 ia = aend
281 281 ib = bend
282 282
283 283 def minimize(self, merge_regions):
284 284 """Trim conflict regions of lines where A and B sides match.
285 285
286 286 Lines where both A and B have made the same changes at the beginning
287 287 or the end of each merge region are eliminated from the conflict
288 288 region and are instead considered the same.
289 289 """
290 290 for region in merge_regions:
291 291 if region[0] != b"conflict":
292 292 yield region
293 293 continue
294 294 # pytype thinks this tuple contains only 3 things, but
295 295 # that's clearly not true because this code successfully
296 296 # executes. It might be wise to rework merge_regions to be
297 297 # some kind of attrs type.
298 298 (
299 299 issue,
300 300 z1,
301 301 z2,
302 302 a1,
303 303 a2,
304 304 b1,
305 305 b2,
306 306 ) = region # pytype: disable=bad-unpacking
307 307 alen = a2 - a1
308 308 blen = b2 - b1
309 309
310 310 # find matches at the front
311 311 ii = 0
312 312 while (
313 313 ii < alen and ii < blen and self.a[a1 + ii] == self.b[b1 + ii]
314 314 ):
315 315 ii += 1
316 316 startmatches = ii
317 317
318 318 # find matches at the end
319 319 ii = 0
320 320 while (
321 321 ii < alen
322 322 and ii < blen
323 323 and self.a[a2 - ii - 1] == self.b[b2 - ii - 1]
324 324 ):
325 325 ii += 1
326 326 endmatches = ii
327 327
328 328 if startmatches > 0:
329 329 yield b'same', a1, a1 + startmatches
330 330
331 331 yield (
332 332 b'conflict',
333 333 z1,
334 334 z2,
335 335 a1 + startmatches,
336 336 a2 - endmatches,
337 337 b1 + startmatches,
338 338 b2 - endmatches,
339 339 )
340 340
341 341 if endmatches > 0:
342 342 yield b'same', a2 - endmatches, a2
343 343
344 344 def find_sync_regions(self):
345 345 """Return a list of sync regions, where both descendants match the base.
346 346
347 347 Generates a list of (base1, base2, a1, a2, b1, b2). There is
348 348 always a zero-length sync region at the end of all the files.
349 349 """
350 350
351 351 ia = ib = 0
352 352 amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
353 353 bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
354 354 len_a = len(amatches)
355 355 len_b = len(bmatches)
356 356
357 357 sl = []
358 358
359 359 while ia < len_a and ib < len_b:
360 360 abase, amatch, alen = amatches[ia]
361 361 bbase, bmatch, blen = bmatches[ib]
362 362
363 363 # there is an unconflicted block at i; how long does it
364 364 # extend? until whichever one ends earlier.
365 365 i = intersect((abase, abase + alen), (bbase, bbase + blen))
366 366 if i:
367 367 intbase = i[0]
368 368 intend = i[1]
369 369 intlen = intend - intbase
370 370
371 371 # found a match of base[i[0], i[1]]; this may be less than
372 372 # the region that matches in either one
373 373 assert intlen <= alen
374 374 assert intlen <= blen
375 375 assert abase <= intbase
376 376 assert bbase <= intbase
377 377
378 378 asub = amatch + (intbase - abase)
379 379 bsub = bmatch + (intbase - bbase)
380 380 aend = asub + intlen
381 381 bend = bsub + intlen
382 382
383 383 assert self.base[intbase:intend] == self.a[asub:aend], (
384 384 self.base[intbase:intend],
385 385 self.a[asub:aend],
386 386 )
387 387
388 388 assert self.base[intbase:intend] == self.b[bsub:bend]
389 389
390 390 sl.append((intbase, intend, asub, aend, bsub, bend))
391 391
392 392 # advance whichever one ends first in the base text
393 393 if (abase + alen) < (bbase + blen):
394 394 ia += 1
395 395 else:
396 396 ib += 1
397 397
398 398 intbase = len(self.base)
399 399 abase = len(self.a)
400 400 bbase = len(self.b)
401 401 sl.append((intbase, intbase, abase, abase, bbase, bbase))
402 402
403 403 return sl
404 404
405 def find_unconflicted(self):
406 """Return a list of ranges in base that are not conflicted."""
407 am = mdiff.get_matching_blocks(self.basetext, self.atext)
408 bm = mdiff.get_matching_blocks(self.basetext, self.btext)
409
410 unc = []
411
412 while am and bm:
413 # there is an unconflicted block at i; how long does it
414 # extend? until whichever one ends earlier.
415 a1 = am[0][0]
416 a2 = a1 + am[0][2]
417 b1 = bm[0][0]
418 b2 = b1 + bm[0][2]
419 i = intersect((a1, a2), (b1, b2))
420 if i:
421 unc.append(i)
422
423 if a2 < b2:
424 del am[0]
425 else:
426 del bm[0]
427
428 return unc
429
430 405
431 406 def _verifytext(text, path, ui, opts):
432 407 """verifies that text is non-binary (unless opts[text] is passed,
433 408 then we just warn)"""
434 409 if stringutil.binary(text):
435 410 msg = _(b"%s looks like a binary file.") % path
436 411 if not opts.get('quiet'):
437 412 ui.warn(_(b'warning: %s\n') % msg)
438 413 if not opts.get('text'):
439 414 raise error.Abort(msg)
440 415 return text
441 416
442 417
443 418 def _picklabels(defaults, overrides):
444 419 if len(overrides) > 3:
445 420 raise error.Abort(_(b"can only specify three labels."))
446 421 result = defaults[:]
447 422 for i, override in enumerate(overrides):
448 423 result[i] = override
449 424 return result
450 425
451 426
452 427 def is_not_null(ctx):
453 428 if not util.safehasattr(ctx, "node"):
454 429 return False
455 430 return ctx.node() != nullid
456 431
457 432
458 433 def _mergediff(m3, name_a, name_b, name_base):
459 434 lines = []
460 435 conflicts = False
461 436 for group in m3.merge_groups():
462 437 if group[0] == b'conflict':
463 438 base_lines, a_lines, b_lines = group[1:]
464 439 base_text = b''.join(base_lines)
465 440 b_blocks = list(
466 441 mdiff.allblocks(
467 442 base_text,
468 443 b''.join(b_lines),
469 444 lines1=base_lines,
470 445 lines2=b_lines,
471 446 )
472 447 )
473 448 a_blocks = list(
474 449 mdiff.allblocks(
475 450 base_text,
476 451 b''.join(a_lines),
477 452 lines1=base_lines,
478 453 lines2=b_lines,
479 454 )
480 455 )
481 456
482 457 def matching_lines(blocks):
483 458 return sum(
484 459 block[1] - block[0]
485 460 for block, kind in blocks
486 461 if kind == b'='
487 462 )
488 463
489 464 def diff_lines(blocks, lines1, lines2):
490 465 for block, kind in blocks:
491 466 if kind == b'=':
492 467 for line in lines1[block[0] : block[1]]:
493 468 yield b' ' + line
494 469 else:
495 470 for line in lines1[block[0] : block[1]]:
496 471 yield b'-' + line
497 472 for line in lines2[block[2] : block[3]]:
498 473 yield b'+' + line
499 474
500 475 lines.append(b"<<<<<<<\n")
501 476 if matching_lines(a_blocks) < matching_lines(b_blocks):
502 477 lines.append(b"======= %s\n" % name_a)
503 478 lines.extend(a_lines)
504 479 lines.append(b"------- %s\n" % name_base)
505 480 lines.append(b"+++++++ %s\n" % name_b)
506 481 lines.extend(diff_lines(b_blocks, base_lines, b_lines))
507 482 else:
508 483 lines.append(b"------- %s\n" % name_base)
509 484 lines.append(b"+++++++ %s\n" % name_a)
510 485 lines.extend(diff_lines(a_blocks, base_lines, a_lines))
511 486 lines.append(b"======= %s\n" % name_b)
512 487 lines.extend(b_lines)
513 488 lines.append(b">>>>>>>\n")
514 489 conflicts = True
515 490 else:
516 491 lines.extend(group[1])
517 492 return lines, conflicts
518 493
519 494
520 495 def simplemerge(ui, localctx, basectx, otherctx, **opts):
521 496 """Performs the simplemerge algorithm.
522 497
523 498 The merged result is written into `localctx`.
524 499 """
525 500
526 501 def readctx(ctx):
527 502 # Merges were always run in the working copy before, which means
528 503 # they used decoded data, if the user defined any repository
529 504 # filters.
530 505 #
531 506 # Maintain that behavior today for BC, though perhaps in the future
532 507 # it'd be worth considering whether merging encoded data (what the
533 508 # repository usually sees) might be more useful.
534 509 return _verifytext(ctx.decodeddata(), ctx.path(), ui, opts)
535 510
536 511 mode = opts.get('mode', b'merge')
537 512 name_a, name_b, name_base = None, None, None
538 513 if mode != b'union':
539 514 name_a, name_b, name_base = _picklabels(
540 515 [localctx.path(), otherctx.path(), None], opts.get('label', [])
541 516 )
542 517
543 518 try:
544 519 localtext = readctx(localctx)
545 520 basetext = readctx(basectx)
546 521 othertext = readctx(otherctx)
547 522 except error.Abort:
548 523 return 1
549 524
550 525 m3 = Merge3Text(basetext, localtext, othertext)
551 526 extrakwargs = {
552 527 b"localorother": opts.get("localorother", None),
553 528 b'minimize': True,
554 529 }
555 530 if mode == b'union':
556 531 extrakwargs[b'start_marker'] = None
557 532 extrakwargs[b'mid_marker'] = None
558 533 extrakwargs[b'end_marker'] = None
559 534 elif name_base is not None:
560 535 extrakwargs[b'base_marker'] = b'|||||||'
561 536 extrakwargs[b'name_base'] = name_base
562 537 extrakwargs[b'minimize'] = False
563 538
564 539 if mode == b'mergediff':
565 540 lines, conflicts = _mergediff(m3, name_a, name_b, name_base)
566 541 else:
567 542 lines = list(
568 543 m3.merge_lines(
569 544 name_a=name_a, name_b=name_b, **pycompat.strkwargs(extrakwargs)
570 545 )
571 546 )
572 547 conflicts = m3.conflicts
573 548
574 549 # merge flags if necessary
575 550 flags = localctx.flags()
576 551 localflags = set(pycompat.iterbytestr(flags))
577 552 otherflags = set(pycompat.iterbytestr(otherctx.flags()))
578 553 if is_not_null(basectx) and localflags != otherflags:
579 554 baseflags = set(pycompat.iterbytestr(basectx.flags()))
580 555 commonflags = localflags & otherflags
581 556 addedflags = (localflags ^ otherflags) - baseflags
582 557 flags = b''.join(sorted(commonflags | addedflags))
583 558
584 559 mergedtext = b''.join(lines)
585 560 if opts.get('print'):
586 561 ui.fout.write(mergedtext)
587 562 else:
588 563 localctx.write(mergedtext, flags)
589 564
590 565 if conflicts and not mode == b'union':
591 566 return 1
@@ -1,396 +1,386 b''
1 1 # Copyright (C) 2004, 2005 Canonical Ltd
2 2 #
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.
7 7 #
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.
12 12 #
13 13 # You should have received a copy of the GNU General Public License
14 14 # along with this program; if not, see <http://www.gnu.org/licenses/>.
15 15
16 16 from __future__ import absolute_import
17 17
18 18 import unittest
19 19 from mercurial import (
20 20 error,
21 21 simplemerge,
22 22 util,
23 23 )
24 24
25 25 from mercurial.utils import stringutil
26 26
27 27 TestCase = unittest.TestCase
28 28 # bzr compatible interface, for the tests
29 29 class Merge3(simplemerge.Merge3Text):
30 30 """3-way merge of texts.
31 31
32 32 Given BASE, OTHER, THIS, tries to produce a combined text
33 33 incorporating the changes from both BASE->OTHER and BASE->THIS.
34 34 All three will typically be sequences of lines."""
35 35
36 36 def __init__(self, base, a, b):
37 37 basetext = b'\n'.join([i.strip(b'\n') for i in base] + [b''])
38 38 atext = b'\n'.join([i.strip(b'\n') for i in a] + [b''])
39 39 btext = b'\n'.join([i.strip(b'\n') for i in b] + [b''])
40 40 if (
41 41 stringutil.binary(basetext)
42 42 or stringutil.binary(atext)
43 43 or stringutil.binary(btext)
44 44 ):
45 45 raise error.Abort(b"don't know how to merge binary files")
46 46 simplemerge.Merge3Text.__init__(
47 47 self, basetext, atext, btext, base, a, b
48 48 )
49 49
50 50
51 51 CantReprocessAndShowBase = simplemerge.CantReprocessAndShowBase
52 52
53 53
54 54 def split_lines(t):
55 55 return util.stringio(t).readlines()
56 56
57 57
58 58 ############################################################
59 59 # test case data from the gnu diffutils manual
60 60 # common base
61 61 TZU = split_lines(
62 62 b""" The Nameless is the origin of Heaven and Earth;
63 63 The named is the mother of all things.
64 64
65 65 Therefore let there always be non-being,
66 66 so we may see their subtlety,
67 67 And let there always be being,
68 68 so we may see their outcome.
69 69 The two are the same,
70 70 But after they are produced,
71 71 they have different names.
72 72 They both may be called deep and profound.
73 73 Deeper and more profound,
74 74 The door of all subtleties!
75 75 """
76 76 )
77 77
78 78 LAO = split_lines(
79 79 b""" The Way that can be told of is not the eternal Way;
80 80 The name that can be named is not the eternal name.
81 81 The Nameless is the origin of Heaven and Earth;
82 82 The Named is the mother of all things.
83 83 Therefore let there always be non-being,
84 84 so we may see their subtlety,
85 85 And let there always be being,
86 86 so we may see their outcome.
87 87 The two are the same,
88 88 But after they are produced,
89 89 they have different names.
90 90 """
91 91 )
92 92
93 93
94 94 TAO = split_lines(
95 95 b""" The Way that can be told of is not the eternal Way;
96 96 The name that can be named is not the eternal name.
97 97 The Nameless is the origin of Heaven and Earth;
98 98 The named is the mother of all things.
99 99
100 100 Therefore let there always be non-being,
101 101 so we may see their subtlety,
102 102 And let there always be being,
103 103 so we may see their result.
104 104 The two are the same,
105 105 But after they are produced,
106 106 they have different names.
107 107
108 108 -- The Way of Lao-Tzu, tr. Wing-tsit Chan
109 109
110 110 """
111 111 )
112 112
113 113 MERGED_RESULT = split_lines(
114 114 b"""\
115 115 The Way that can be told of is not the eternal Way;
116 116 The name that can be named is not the eternal name.
117 117 The Nameless is the origin of Heaven and Earth;
118 118 The Named is the mother of all things.
119 119 Therefore let there always be non-being,
120 120 so we may see their subtlety,
121 121 And let there always be being,
122 122 so we may see their result.
123 123 The two are the same,
124 124 But after they are produced,
125 125 they have different names.\
126 126 \n<<<<<<< LAO\
127 127 \n=======
128 128
129 129 -- The Way of Lao-Tzu, tr. Wing-tsit Chan
130 130 \
131 131 \n>>>>>>> TAO
132 132 """
133 133 )
134 134
135 135
136 136 class TestMerge3(TestCase):
137 137 def log(self, msg):
138 138 pass
139 139
140 140 def test_no_changes(self):
141 141 """No conflicts because nothing changed"""
142 142 m3 = Merge3([b'aaa', b'bbb'], [b'aaa', b'bbb'], [b'aaa', b'bbb'])
143 143
144 self.assertEqual(m3.find_unconflicted(), [(0, 2)])
145
146 144 self.assertEqual(
147 145 list(m3.find_sync_regions()),
148 146 [(0, 2, 0, 2, 0, 2), (2, 2, 2, 2, 2, 2)],
149 147 )
150 148
151 149 self.assertEqual(list(m3.merge_regions()), [(b'unchanged', 0, 2)])
152 150
153 151 self.assertEqual(
154 152 list(m3.merge_groups()), [(b'unchanged', [b'aaa', b'bbb'])]
155 153 )
156 154
157 155 def test_front_insert(self):
158 156 m3 = Merge3([b'zz'], [b'aaa', b'bbb', b'zz'], [b'zz'])
159 157
160 158 # todo: should use a sentinel at end as from get_matching_blocks
161 159 # to match without zz
162 160 self.assertEqual(
163 161 list(m3.find_sync_regions()),
164 162 [(0, 1, 2, 3, 0, 1), (1, 1, 3, 3, 1, 1)],
165 163 )
166 164
167 165 self.assertEqual(
168 166 list(m3.merge_regions()), [(b'a', 0, 2), (b'unchanged', 0, 1)]
169 167 )
170 168
171 169 self.assertEqual(
172 170 list(m3.merge_groups()),
173 171 [(b'a', [b'aaa', b'bbb']), (b'unchanged', [b'zz'])],
174 172 )
175 173
176 174 def test_null_insert(self):
177 175 m3 = Merge3([], [b'aaa', b'bbb'], [])
178 176 # todo: should use a sentinel at end as from get_matching_blocks
179 177 # to match without zz
180 178 self.assertEqual(list(m3.find_sync_regions()), [(0, 0, 2, 2, 0, 0)])
181 179
182 180 self.assertEqual(list(m3.merge_regions()), [(b'a', 0, 2)])
183 181
184 182 self.assertEqual(list(m3.merge_lines()), [b'aaa', b'bbb'])
185 183
186 184 def test_no_conflicts(self):
187 185 """No conflicts because only one side changed"""
188 186 m3 = Merge3(
189 187 [b'aaa', b'bbb'], [b'aaa', b'111', b'bbb'], [b'aaa', b'bbb']
190 188 )
191 189
192 self.assertEqual(m3.find_unconflicted(), [(0, 1), (1, 2)])
193
194 190 self.assertEqual(
195 191 list(m3.find_sync_regions()),
196 192 [(0, 1, 0, 1, 0, 1), (1, 2, 2, 3, 1, 2), (2, 2, 3, 3, 2, 2)],
197 193 )
198 194
199 195 self.assertEqual(
200 196 list(m3.merge_regions()),
201 197 [(b'unchanged', 0, 1), (b'a', 1, 2), (b'unchanged', 1, 2)],
202 198 )
203 199
204 200 def test_append_a(self):
205 201 m3 = Merge3(
206 202 [b'aaa\n', b'bbb\n'],
207 203 [b'aaa\n', b'bbb\n', b'222\n'],
208 204 [b'aaa\n', b'bbb\n'],
209 205 )
210 206
211 207 self.assertEqual(b''.join(m3.merge_lines()), b'aaa\nbbb\n222\n')
212 208
213 209 def test_append_b(self):
214 210 m3 = Merge3(
215 211 [b'aaa\n', b'bbb\n'],
216 212 [b'aaa\n', b'bbb\n'],
217 213 [b'aaa\n', b'bbb\n', b'222\n'],
218 214 )
219 215
220 216 self.assertEqual(b''.join(m3.merge_lines()), b'aaa\nbbb\n222\n')
221 217
222 218 def test_append_agreement(self):
223 219 m3 = Merge3(
224 220 [b'aaa\n', b'bbb\n'],
225 221 [b'aaa\n', b'bbb\n', b'222\n'],
226 222 [b'aaa\n', b'bbb\n', b'222\n'],
227 223 )
228 224
229 225 self.assertEqual(b''.join(m3.merge_lines()), b'aaa\nbbb\n222\n')
230 226
231 227 def test_append_clash(self):
232 228 m3 = Merge3(
233 229 [b'aaa\n', b'bbb\n'],
234 230 [b'aaa\n', b'bbb\n', b'222\n'],
235 231 [b'aaa\n', b'bbb\n', b'333\n'],
236 232 )
237 233
238 234 ml = m3.merge_lines(
239 235 name_a=b'a',
240 236 name_b=b'b',
241 237 start_marker=b'<<',
242 238 mid_marker=b'--',
243 239 end_marker=b'>>',
244 240 )
245 241 self.assertEqual(
246 242 b''.join(ml),
247 243 b'aaa\n' b'bbb\n' b'<< a\n' b'222\n' b'--\n' b'333\n' b'>> b\n',
248 244 )
249 245
250 246 def test_insert_agreement(self):
251 247 m3 = Merge3(
252 248 [b'aaa\n', b'bbb\n'],
253 249 [b'aaa\n', b'222\n', b'bbb\n'],
254 250 [b'aaa\n', b'222\n', b'bbb\n'],
255 251 )
256 252
257 253 ml = m3.merge_lines(
258 254 name_a=b'a',
259 255 name_b=b'b',
260 256 start_marker=b'<<',
261 257 mid_marker=b'--',
262 258 end_marker=b'>>',
263 259 )
264 260 self.assertEqual(b''.join(ml), b'aaa\n222\nbbb\n')
265 261
266 262 def test_insert_clash(self):
267 263 """Both try to insert lines in the same place."""
268 264 m3 = Merge3(
269 265 [b'aaa\n', b'bbb\n'],
270 266 [b'aaa\n', b'111\n', b'bbb\n'],
271 267 [b'aaa\n', b'222\n', b'bbb\n'],
272 268 )
273 269
274 self.assertEqual(m3.find_unconflicted(), [(0, 1), (1, 2)])
275
276 270 self.assertEqual(
277 271 list(m3.find_sync_regions()),
278 272 [(0, 1, 0, 1, 0, 1), (1, 2, 2, 3, 2, 3), (2, 2, 3, 3, 3, 3)],
279 273 )
280 274
281 275 self.assertEqual(
282 276 list(m3.merge_regions()),
283 277 [
284 278 (b'unchanged', 0, 1),
285 279 (b'conflict', 1, 1, 1, 2, 1, 2),
286 280 (b'unchanged', 1, 2),
287 281 ],
288 282 )
289 283
290 284 self.assertEqual(
291 285 list(m3.merge_groups()),
292 286 [
293 287 (b'unchanged', [b'aaa\n']),
294 288 (b'conflict', [], [b'111\n'], [b'222\n']),
295 289 (b'unchanged', [b'bbb\n']),
296 290 ],
297 291 )
298 292
299 293 ml = m3.merge_lines(
300 294 name_a=b'a',
301 295 name_b=b'b',
302 296 start_marker=b'<<',
303 297 mid_marker=b'--',
304 298 end_marker=b'>>',
305 299 )
306 300 self.assertEqual(
307 301 b''.join(ml),
308 302 b'''aaa
309 303 << a
310 304 111
311 305 --
312 306 222
313 307 >> b
314 308 bbb
315 309 ''',
316 310 )
317 311
318 312 def test_replace_clash(self):
319 313 """Both try to insert lines in the same place."""
320 314 m3 = Merge3(
321 315 [b'aaa', b'000', b'bbb'],
322 316 [b'aaa', b'111', b'bbb'],
323 317 [b'aaa', b'222', b'bbb'],
324 318 )
325 319
326 self.assertEqual(m3.find_unconflicted(), [(0, 1), (2, 3)])
327
328 320 self.assertEqual(
329 321 list(m3.find_sync_regions()),
330 322 [(0, 1, 0, 1, 0, 1), (2, 3, 2, 3, 2, 3), (3, 3, 3, 3, 3, 3)],
331 323 )
332 324
333 325 def test_replace_multi(self):
334 326 """Replacement with regions of different size."""
335 327 m3 = Merge3(
336 328 [b'aaa', b'000', b'000', b'bbb'],
337 329 [b'aaa', b'111', b'111', b'111', b'bbb'],
338 330 [b'aaa', b'222', b'222', b'222', b'222', b'bbb'],
339 331 )
340 332
341 self.assertEqual(m3.find_unconflicted(), [(0, 1), (3, 4)])
342
343 333 self.assertEqual(
344 334 list(m3.find_sync_regions()),
345 335 [(0, 1, 0, 1, 0, 1), (3, 4, 4, 5, 5, 6), (4, 4, 5, 5, 6, 6)],
346 336 )
347 337
348 338 def test_merge_poem(self):
349 339 """Test case from diff3 manual"""
350 340 m3 = Merge3(TZU, LAO, TAO)
351 341 ml = list(m3.merge_lines(b'LAO', b'TAO'))
352 342 self.log(b'merge result:')
353 343 self.log(b''.join(ml))
354 344 self.assertEqual(ml, MERGED_RESULT)
355 345
356 346 def test_binary(self):
357 347 with self.assertRaises(error.Abort):
358 348 Merge3([b'\x00'], [b'a'], [b'b'])
359 349
360 350 def test_dos_text(self):
361 351 base_text = b'a\r\n'
362 352 this_text = b'b\r\n'
363 353 other_text = b'c\r\n'
364 354 m3 = Merge3(
365 355 base_text.splitlines(True),
366 356 other_text.splitlines(True),
367 357 this_text.splitlines(True),
368 358 )
369 359 m_lines = m3.merge_lines(b'OTHER', b'THIS')
370 360 self.assertEqual(
371 361 b'<<<<<<< OTHER\r\nc\r\n=======\r\nb\r\n'
372 362 b'>>>>>>> THIS\r\n'.splitlines(True),
373 363 list(m_lines),
374 364 )
375 365
376 366 def test_mac_text(self):
377 367 base_text = b'a\r'
378 368 this_text = b'b\r'
379 369 other_text = b'c\r'
380 370 m3 = Merge3(
381 371 base_text.splitlines(True),
382 372 other_text.splitlines(True),
383 373 this_text.splitlines(True),
384 374 )
385 375 m_lines = m3.merge_lines(b'OTHER', b'THIS')
386 376 self.assertEqual(
387 377 b'<<<<<<< OTHER\rc\r=======\rb\r'
388 378 b'>>>>>>> THIS\r'.splitlines(True),
389 379 list(m_lines),
390 380 )
391 381
392 382
393 383 if __name__ == '__main__':
394 384 import silenttestrunner
395 385
396 386 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now