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