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