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