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