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