##// END OF EJS Templates
simplemerge: change _minimize() to minimize a single conflict...
Martin von Zweigbergk -
r49396:da04f362 default draft
parent child Browse files
Show More
@@ -1,530 +1,519 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 217 def find_sync_regions(self):
218 218 """Return a list of sync regions, where both descendants match the base.
219 219
220 220 Generates a list of (base1, base2, a1, a2, b1, b2). There is
221 221 always a zero-length sync region at the end of all the files.
222 222 """
223 223
224 224 ia = ib = 0
225 225 amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
226 226 bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
227 227 len_a = len(amatches)
228 228 len_b = len(bmatches)
229 229
230 230 sl = []
231 231
232 232 while ia < len_a and ib < len_b:
233 233 abase, amatch, alen = amatches[ia]
234 234 bbase, bmatch, blen = bmatches[ib]
235 235
236 236 # there is an unconflicted block at i; how long does it
237 237 # extend? until whichever one ends earlier.
238 238 i = intersect((abase, abase + alen), (bbase, bbase + blen))
239 239 if i:
240 240 intbase = i[0]
241 241 intend = i[1]
242 242 intlen = intend - intbase
243 243
244 244 # found a match of base[i[0], i[1]]; this may be less than
245 245 # the region that matches in either one
246 246 assert intlen <= alen
247 247 assert intlen <= blen
248 248 assert abase <= intbase
249 249 assert bbase <= intbase
250 250
251 251 asub = amatch + (intbase - abase)
252 252 bsub = bmatch + (intbase - bbase)
253 253 aend = asub + intlen
254 254 bend = bsub + intlen
255 255
256 256 assert self.base[intbase:intend] == self.a[asub:aend], (
257 257 self.base[intbase:intend],
258 258 self.a[asub:aend],
259 259 )
260 260
261 261 assert self.base[intbase:intend] == self.b[bsub:bend]
262 262
263 263 sl.append((intbase, intend, asub, aend, bsub, bend))
264 264
265 265 # advance whichever one ends first in the base text
266 266 if (abase + alen) < (bbase + blen):
267 267 ia += 1
268 268 else:
269 269 ib += 1
270 270
271 271 intbase = len(self.base)
272 272 abase = len(self.a)
273 273 bbase = len(self.b)
274 274 sl.append((intbase, intbase, abase, abase, bbase, bbase))
275 275
276 276 return sl
277 277
278 278
279 279 def _verifytext(text, path, ui, opts):
280 280 """verifies that text is non-binary (unless opts[text] is passed,
281 281 then we just warn)"""
282 282 if stringutil.binary(text):
283 283 msg = _(b"%s looks like a binary file.") % path
284 284 if not opts.get('quiet'):
285 285 ui.warn(_(b'warning: %s\n') % msg)
286 286 if not opts.get('text'):
287 287 raise error.Abort(msg)
288 288 return text
289 289
290 290
291 291 def _picklabels(overrides):
292 292 if len(overrides) > 3:
293 293 raise error.Abort(_(b"can only specify three labels."))
294 294 result = [None, None, None]
295 295 for i, override in enumerate(overrides):
296 296 result[i] = override
297 297 return result
298 298
299 299
300 300 def _detect_newline(m3):
301 301 if len(m3.a) > 0:
302 302 if m3.a[0].endswith(b'\r\n'):
303 303 return b'\r\n'
304 304 elif m3.a[0].endswith(b'\r'):
305 305 return b'\r'
306 306 return b'\n'
307 307
308 308
309 def _minimize(merge_groups):
309 def _minimize(a_lines, b_lines):
310 310 """Trim conflict regions of lines where A and B sides match.
311 311
312 312 Lines where both A and B have made the same changes at the beginning
313 313 or the end of each merge region are eliminated from the conflict
314 314 region and are instead considered the same.
315 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)
316 alen = len(a_lines)
317 blen = len(b_lines)
323 318
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
319 # find matches at the front
320 ii = 0
321 while ii < alen and ii < blen and a_lines[ii] == b_lines[ii]:
322 ii += 1
323 startmatches = ii
329 324
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]
325 # find matches at the end
326 ii = 0
327 while ii < alen and ii < blen and a_lines[-ii - 1] == b_lines[-ii - 1]:
328 ii += 1
329 endmatches = ii
338 330
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 :]
331 lines_before = a_lines[:startmatches]
332 new_a_lines = a_lines[startmatches : alen - endmatches]
333 new_b_lines = b_lines[startmatches : blen - endmatches]
334 lines_after = a_lines[alen - endmatches :]
335 return lines_before, new_a_lines, new_b_lines, lines_after
350 336
351 337
352 338 def render_minimized(
353 339 m3,
354 340 name_a=None,
355 341 name_b=None,
356 342 start_marker=b'<<<<<<<',
357 343 mid_marker=b'=======',
358 344 end_marker=b'>>>>>>>',
359 345 ):
360 346 """Return merge in cvs-like form."""
361 347 newline = _detect_newline(m3)
362 348 conflicts = False
363 349 if name_a:
364 350 start_marker = start_marker + b' ' + name_a
365 351 if name_b:
366 352 end_marker = end_marker + b' ' + name_b
367 353 merge_groups = m3.merge_groups()
368 merge_groups = _minimize(merge_groups)
369 354 lines = []
370 355 for what, group_lines in merge_groups:
371 356 if what == b'conflict':
357 conflicts = True
372 358 base_lines, a_lines, b_lines = group_lines
373 conflicts = True
359 minimized = _minimize(a_lines, b_lines)
360 lines_before, a_lines, b_lines, lines_after = minimized
361 lines.extend(lines_before)
374 362 lines.append(start_marker + newline)
375 363 lines.extend(a_lines)
376 364 lines.append(mid_marker + newline)
377 365 lines.extend(b_lines)
378 366 lines.append(end_marker + newline)
367 lines.extend(lines_after)
379 368 else:
380 369 lines.extend(group_lines)
381 370 return lines, conflicts
382 371
383 372
384 373 def render_merge3(m3, name_a, name_b, name_base):
385 374 """Render conflicts as 3-way conflict markers."""
386 375 newline = _detect_newline(m3)
387 376 conflicts = False
388 377 lines = []
389 378 for what, group_lines in m3.merge_groups():
390 379 if what == b'conflict':
391 380 base_lines, a_lines, b_lines = group_lines
392 381 conflicts = True
393 382 lines.append(b'<<<<<<< ' + name_a + newline)
394 383 lines.extend(a_lines)
395 384 lines.append(b'||||||| ' + name_base + newline)
396 385 lines.extend(base_lines)
397 386 lines.append(b'=======' + newline)
398 387 lines.extend(b_lines)
399 388 lines.append(b'>>>>>>> ' + name_b + newline)
400 389 else:
401 390 lines.extend(group_lines)
402 391 return lines, conflicts
403 392
404 393
405 394 def render_mergediff(m3, name_a, name_b, name_base):
406 395 """Render conflicts as conflict markers with one snapshot and one diff."""
407 396 newline = _detect_newline(m3)
408 397 lines = []
409 398 conflicts = False
410 399 for what, group_lines in m3.merge_groups():
411 400 if what == b'conflict':
412 401 base_lines, a_lines, b_lines = group_lines
413 402 base_text = b''.join(base_lines)
414 403 b_blocks = list(
415 404 mdiff.allblocks(
416 405 base_text,
417 406 b''.join(b_lines),
418 407 lines1=base_lines,
419 408 lines2=b_lines,
420 409 )
421 410 )
422 411 a_blocks = list(
423 412 mdiff.allblocks(
424 413 base_text,
425 414 b''.join(a_lines),
426 415 lines1=base_lines,
427 416 lines2=b_lines,
428 417 )
429 418 )
430 419
431 420 def matching_lines(blocks):
432 421 return sum(
433 422 block[1] - block[0]
434 423 for block, kind in blocks
435 424 if kind == b'='
436 425 )
437 426
438 427 def diff_lines(blocks, lines1, lines2):
439 428 for block, kind in blocks:
440 429 if kind == b'=':
441 430 for line in lines1[block[0] : block[1]]:
442 431 yield b' ' + line
443 432 else:
444 433 for line in lines1[block[0] : block[1]]:
445 434 yield b'-' + line
446 435 for line in lines2[block[2] : block[3]]:
447 436 yield b'+' + line
448 437
449 438 lines.append(b"<<<<<<<" + newline)
450 439 if matching_lines(a_blocks) < matching_lines(b_blocks):
451 440 lines.append(b"======= " + name_a + newline)
452 441 lines.extend(a_lines)
453 442 lines.append(b"------- " + name_base + newline)
454 443 lines.append(b"+++++++ " + name_b + newline)
455 444 lines.extend(diff_lines(b_blocks, base_lines, b_lines))
456 445 else:
457 446 lines.append(b"------- " + name_base + newline)
458 447 lines.append(b"+++++++ " + name_a + newline)
459 448 lines.extend(diff_lines(a_blocks, base_lines, a_lines))
460 449 lines.append(b"======= " + name_b + newline)
461 450 lines.extend(b_lines)
462 451 lines.append(b">>>>>>>" + newline)
463 452 conflicts = True
464 453 else:
465 454 lines.extend(group_lines)
466 455 return lines, conflicts
467 456
468 457
469 458 def _resolve(m3, sides):
470 459 lines = []
471 460 for what, group_lines in m3.merge_groups():
472 461 if what == b'conflict':
473 462 for side in sides:
474 463 lines.extend(group_lines[side])
475 464 else:
476 465 lines.extend(group_lines)
477 466 return lines
478 467
479 468
480 469 def simplemerge(ui, localctx, basectx, otherctx, **opts):
481 470 """Performs the simplemerge algorithm.
482 471
483 472 The merged result is written into `localctx`.
484 473 """
485 474
486 475 def readctx(ctx):
487 476 # Merges were always run in the working copy before, which means
488 477 # they used decoded data, if the user defined any repository
489 478 # filters.
490 479 #
491 480 # Maintain that behavior today for BC, though perhaps in the future
492 481 # it'd be worth considering whether merging encoded data (what the
493 482 # repository usually sees) might be more useful.
494 483 return _verifytext(ctx.decodeddata(), ctx.path(), ui, opts)
495 484
496 485 try:
497 486 localtext = readctx(localctx)
498 487 basetext = readctx(basectx)
499 488 othertext = readctx(otherctx)
500 489 except error.Abort:
501 490 return 1
502 491
503 492 m3 = Merge3Text(basetext, localtext, othertext)
504 493 conflicts = False
505 494 mode = opts.get('mode', b'merge')
506 495 if mode == b'union':
507 496 lines = _resolve(m3, (1, 2))
508 497 elif mode == b'local':
509 498 lines = _resolve(m3, (1,))
510 499 elif mode == b'other':
511 500 lines = _resolve(m3, (2,))
512 501 else:
513 502 name_a, name_b, name_base = _picklabels(opts.get('label', []))
514 503 if mode == b'mergediff':
515 504 lines, conflicts = render_mergediff(m3, name_a, name_b, name_base)
516 505 elif mode == b'merge3':
517 506 lines, conflicts = render_merge3(m3, name_a, name_b, name_base)
518 507 else:
519 508 lines, conflicts = render_minimized(m3, name_a, name_b)
520 509
521 510 mergedtext = b''.join(lines)
522 511 if opts.get('print'):
523 512 ui.fout.write(mergedtext)
524 513 else:
525 514 # localctx.flags() already has the merged flags (done in
526 515 # mergestate.resolve())
527 516 localctx.write(mergedtext, localctx.flags())
528 517
529 518 if conflicts:
530 519 return 1
General Comments 0
You need to be logged in to leave comments. Login now