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