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