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