comparison contrib/simplemerge @ 6002:abd66eb0889e

merge: move the bulk of simplemerge into core - keep existing simplemerge command in contrib - clean up test interface
author Matt Mackall <mpm@selenic.com>
date Sun, 03 Feb 2008 19:29:05 -0600
parents ea7b982b6c08
children a6477aa893b8
comparison
equal deleted inserted replaced
6001:30d2fecaab76 6002:abd66eb0889e
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (C) 2004, 2005 Canonical Ltd
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18
19 # mbp: "you know that thing where cvs gives you conflict markers?"
20 # s: "i hate that."
21 2
22 from mercurial import demandimport 3 from mercurial import demandimport
23 demandimport.enable() 4 demandimport.enable()
24 5
25 from mercurial import util, mdiff, fancyopts 6 import os, sys
26 from mercurial.i18n import _ 7 from mercurial.i18n import _
27 8 from mercurial import simplemerge, fancyopts, util
28
29 class CantReprocessAndShowBase(Exception):
30 pass
31
32
33 def warn(message):
34 sys.stdout.flush()
35 sys.stderr.write(message)
36 sys.stderr.flush()
37
38
39 def intersect(ra, rb):
40 """Given two ranges return the range where they intersect or None.
41
42 >>> intersect((0, 10), (0, 6))
43 (0, 6)
44 >>> intersect((0, 10), (5, 15))
45 (5, 10)
46 >>> intersect((0, 10), (10, 15))
47 >>> intersect((0, 9), (10, 15))
48 >>> intersect((0, 9), (7, 15))
49 (7, 9)
50 """
51 assert ra[0] <= ra[1]
52 assert rb[0] <= rb[1]
53
54 sa = max(ra[0], rb[0])
55 sb = min(ra[1], rb[1])
56 if sa < sb:
57 return sa, sb
58 else:
59 return None
60
61
62 def compare_range(a, astart, aend, b, bstart, bend):
63 """Compare a[astart:aend] == b[bstart:bend], without slicing.
64 """
65 if (aend-astart) != (bend-bstart):
66 return False
67 for ia, ib in zip(xrange(astart, aend), xrange(bstart, bend)):
68 if a[ia] != b[ib]:
69 return False
70 else:
71 return True
72
73
74
75
76 class Merge3Text(object):
77 """3-way merge of texts.
78
79 Given strings BASE, OTHER, THIS, tries to produce a combined text
80 incorporating the changes from both BASE->OTHER and BASE->THIS."""
81 def __init__(self, basetext, atext, btext, base=None, a=None, b=None):
82 self.basetext = basetext
83 self.atext = atext
84 self.btext = btext
85 if base is None:
86 base = mdiff.splitnewlines(basetext)
87 if a is None:
88 a = mdiff.splitnewlines(atext)
89 if b is None:
90 b = mdiff.splitnewlines(btext)
91 self.base = base
92 self.a = a
93 self.b = b
94
95
96
97 def merge_lines(self,
98 name_a=None,
99 name_b=None,
100 name_base=None,
101 start_marker='<<<<<<<',
102 mid_marker='=======',
103 end_marker='>>>>>>>',
104 base_marker=None,
105 reprocess=False):
106 """Return merge in cvs-like form.
107 """
108 self.conflicts = False
109 newline = '\n'
110 if len(self.a) > 0:
111 if self.a[0].endswith('\r\n'):
112 newline = '\r\n'
113 elif self.a[0].endswith('\r'):
114 newline = '\r'
115 if base_marker and reprocess:
116 raise CantReprocessAndShowBase()
117 if name_a:
118 start_marker = start_marker + ' ' + name_a
119 if name_b:
120 end_marker = end_marker + ' ' + name_b
121 if name_base and base_marker:
122 base_marker = base_marker + ' ' + name_base
123 merge_regions = self.merge_regions()
124 if reprocess is True:
125 merge_regions = self.reprocess_merge_regions(merge_regions)
126 for t in merge_regions:
127 what = t[0]
128 if what == 'unchanged':
129 for i in range(t[1], t[2]):
130 yield self.base[i]
131 elif what == 'a' or what == 'same':
132 for i in range(t[1], t[2]):
133 yield self.a[i]
134 elif what == 'b':
135 for i in range(t[1], t[2]):
136 yield self.b[i]
137 elif what == 'conflict':
138 self.conflicts = True
139 yield start_marker + newline
140 for i in range(t[3], t[4]):
141 yield self.a[i]
142 if base_marker is not None:
143 yield base_marker + newline
144 for i in range(t[1], t[2]):
145 yield self.base[i]
146 yield mid_marker + newline
147 for i in range(t[5], t[6]):
148 yield self.b[i]
149 yield end_marker + newline
150 else:
151 raise ValueError(what)
152
153
154
155
156
157 def merge_annotated(self):
158 """Return merge with conflicts, showing origin of lines.
159
160 Most useful for debugging merge.
161 """
162 for t in self.merge_regions():
163 what = t[0]
164 if what == 'unchanged':
165 for i in range(t[1], t[2]):
166 yield 'u | ' + self.base[i]
167 elif what == 'a' or what == 'same':
168 for i in range(t[1], t[2]):
169 yield what[0] + ' | ' + self.a[i]
170 elif what == 'b':
171 for i in range(t[1], t[2]):
172 yield 'b | ' + self.b[i]
173 elif what == 'conflict':
174 yield '<<<<\n'
175 for i in range(t[3], t[4]):
176 yield 'A | ' + self.a[i]
177 yield '----\n'
178 for i in range(t[5], t[6]):
179 yield 'B | ' + self.b[i]
180 yield '>>>>\n'
181 else:
182 raise ValueError(what)
183
184
185
186
187
188 def merge_groups(self):
189 """Yield sequence of line groups. Each one is a tuple:
190
191 'unchanged', lines
192 Lines unchanged from base
193
194 'a', lines
195 Lines taken from a
196
197 'same', lines
198 Lines taken from a (and equal to b)
199
200 'b', lines
201 Lines taken from b
202
203 'conflict', base_lines, a_lines, b_lines
204 Lines from base were changed to either a or b and conflict.
205 """
206 for t in self.merge_regions():
207 what = t[0]
208 if what == 'unchanged':
209 yield what, self.base[t[1]:t[2]]
210 elif what == 'a' or what == 'same':
211 yield what, self.a[t[1]:t[2]]
212 elif what == 'b':
213 yield what, self.b[t[1]:t[2]]
214 elif what == 'conflict':
215 yield (what,
216 self.base[t[1]:t[2]],
217 self.a[t[3]:t[4]],
218 self.b[t[5]:t[6]])
219 else:
220 raise ValueError(what)
221
222
223 def merge_regions(self):
224 """Return sequences of matching and conflicting regions.
225
226 This returns tuples, where the first value says what kind we
227 have:
228
229 'unchanged', start, end
230 Take a region of base[start:end]
231
232 'same', astart, aend
233 b and a are different from base but give the same result
234
235 'a', start, end
236 Non-clashing insertion from a[start:end]
237
238 Method is as follows:
239
240 The two sequences align only on regions which match the base
241 and both descendents. These are found by doing a two-way diff
242 of each one against the base, and then finding the
243 intersections between those regions. These "sync regions"
244 are by definition unchanged in both and easily dealt with.
245
246 The regions in between can be in any of three cases:
247 conflicted, or changed on only one side.
248 """
249
250 # section a[0:ia] has been disposed of, etc
251 iz = ia = ib = 0
252
253 for zmatch, zend, amatch, aend, bmatch, bend in self.find_sync_regions():
254 #print 'match base [%d:%d]' % (zmatch, zend)
255
256 matchlen = zend - zmatch
257 assert matchlen >= 0
258 assert matchlen == (aend - amatch)
259 assert matchlen == (bend - bmatch)
260
261 len_a = amatch - ia
262 len_b = bmatch - ib
263 len_base = zmatch - iz
264 assert len_a >= 0
265 assert len_b >= 0
266 assert len_base >= 0
267
268 #print 'unmatched a=%d, b=%d' % (len_a, len_b)
269
270 if len_a or len_b:
271 # try to avoid actually slicing the lists
272 equal_a = compare_range(self.a, ia, amatch,
273 self.base, iz, zmatch)
274 equal_b = compare_range(self.b, ib, bmatch,
275 self.base, iz, zmatch)
276 same = compare_range(self.a, ia, amatch,
277 self.b, ib, bmatch)
278
279 if same:
280 yield 'same', ia, amatch
281 elif equal_a and not equal_b:
282 yield 'b', ib, bmatch
283 elif equal_b and not equal_a:
284 yield 'a', ia, amatch
285 elif not equal_a and not equal_b:
286 yield 'conflict', iz, zmatch, ia, amatch, ib, bmatch
287 else:
288 raise AssertionError("can't handle a=b=base but unmatched")
289
290 ia = amatch
291 ib = bmatch
292 iz = zmatch
293
294 # if the same part of the base was deleted on both sides
295 # that's OK, we can just skip it.
296
297
298 if matchlen > 0:
299 assert ia == amatch
300 assert ib == bmatch
301 assert iz == zmatch
302
303 yield 'unchanged', zmatch, zend
304 iz = zend
305 ia = aend
306 ib = bend
307
308
309 def reprocess_merge_regions(self, merge_regions):
310 """Where there are conflict regions, remove the agreed lines.
311
312 Lines where both A and B have made the same changes are
313 eliminated.
314 """
315 for region in merge_regions:
316 if region[0] != "conflict":
317 yield region
318 continue
319 type, iz, zmatch, ia, amatch, ib, bmatch = region
320 a_region = self.a[ia:amatch]
321 b_region = self.b[ib:bmatch]
322 matches = mdiff.get_matching_blocks(''.join(a_region),
323 ''.join(b_region))
324 next_a = ia
325 next_b = ib
326 for region_ia, region_ib, region_len in matches[:-1]:
327 region_ia += ia
328 region_ib += ib
329 reg = self.mismatch_region(next_a, region_ia, next_b,
330 region_ib)
331 if reg is not None:
332 yield reg
333 yield 'same', region_ia, region_len+region_ia
334 next_a = region_ia + region_len
335 next_b = region_ib + region_len
336 reg = self.mismatch_region(next_a, amatch, next_b, bmatch)
337 if reg is not None:
338 yield reg
339
340
341 def mismatch_region(next_a, region_ia, next_b, region_ib):
342 if next_a < region_ia or next_b < region_ib:
343 return 'conflict', None, None, next_a, region_ia, next_b, region_ib
344 mismatch_region = staticmethod(mismatch_region)
345
346
347 def find_sync_regions(self):
348 """Return a list of sync regions, where both descendents match the base.
349
350 Generates a list of (base1, base2, a1, a2, b1, b2). There is
351 always a zero-length sync region at the end of all the files.
352 """
353
354 ia = ib = 0
355 amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
356 bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
357 len_a = len(amatches)
358 len_b = len(bmatches)
359
360 sl = []
361
362 while ia < len_a and ib < len_b:
363 abase, amatch, alen = amatches[ia]
364 bbase, bmatch, blen = bmatches[ib]
365
366 # there is an unconflicted block at i; how long does it
367 # extend? until whichever one ends earlier.
368 i = intersect((abase, abase+alen), (bbase, bbase+blen))
369 if i:
370 intbase = i[0]
371 intend = i[1]
372 intlen = intend - intbase
373
374 # found a match of base[i[0], i[1]]; this may be less than
375 # the region that matches in either one
376 assert intlen <= alen
377 assert intlen <= blen
378 assert abase <= intbase
379 assert bbase <= intbase
380
381 asub = amatch + (intbase - abase)
382 bsub = bmatch + (intbase - bbase)
383 aend = asub + intlen
384 bend = bsub + intlen
385
386 assert self.base[intbase:intend] == self.a[asub:aend], \
387 (self.base[intbase:intend], self.a[asub:aend])
388
389 assert self.base[intbase:intend] == self.b[bsub:bend]
390
391 sl.append((intbase, intend,
392 asub, aend,
393 bsub, bend))
394
395 # advance whichever one ends first in the base text
396 if (abase + alen) < (bbase + blen):
397 ia += 1
398 else:
399 ib += 1
400
401 intbase = len(self.base)
402 abase = len(self.a)
403 bbase = len(self.b)
404 sl.append((intbase, intbase, abase, abase, bbase, bbase))
405
406 return sl
407
408
409
410 def find_unconflicted(self):
411 """Return a list of ranges in base that are not conflicted."""
412 am = mdiff.get_matching_blocks(self.basetext, self.atext)
413 bm = mdiff.get_matching_blocks(self.basetext, self.btext)
414
415 unc = []
416
417 while am and bm:
418 # there is an unconflicted block at i; how long does it
419 # extend? until whichever one ends earlier.
420 a1 = am[0][0]
421 a2 = a1 + am[0][2]
422 b1 = bm[0][0]
423 b2 = b1 + bm[0][2]
424 i = intersect((a1, a2), (b1, b2))
425 if i:
426 unc.append(i)
427
428 if a2 < b2:
429 del am[0]
430 else:
431 del bm[0]
432
433 return unc
434
435
436 # bzr compatible interface, for the tests
437 class Merge3(Merge3Text):
438 """3-way merge of texts.
439
440 Given BASE, OTHER, THIS, tries to produce a combined text
441 incorporating the changes from both BASE->OTHER and BASE->THIS.
442 All three will typically be sequences of lines."""
443 def __init__(self, base, a, b):
444 basetext = '\n'.join([i.strip('\n') for i in base] + [''])
445 atext = '\n'.join([i.strip('\n') for i in a] + [''])
446 btext = '\n'.join([i.strip('\n') for i in b] + [''])
447 if util.binary(basetext) or util.binary(atext) or util.binary(btext):
448 raise util.Abort(_("don't know how to merge binary files"))
449 Merge3Text.__init__(self, basetext, atext, btext, base, a, b)
450
451
452 def simplemerge(local, base, other, **opts):
453 def readfile(filename):
454 f = open(filename, "rb")
455 text = f.read()
456 f.close()
457 if util.binary(text):
458 msg = _("%s looks like a binary file.") % filename
459 if not opts.get('text'):
460 raise util.Abort(msg)
461 elif not opts.get('quiet'):
462 warn(_('warning: %s\n') % msg)
463 return text
464
465 name_a = local
466 name_b = other
467 labels = opts.get('label', [])
468 if labels:
469 name_a = labels.pop(0)
470 if labels:
471 name_b = labels.pop(0)
472 if labels:
473 raise util.Abort(_("can only specify two labels."))
474
475 localtext = readfile(local)
476 basetext = readfile(base)
477 othertext = readfile(other)
478
479 orig = local
480 local = os.path.realpath(local)
481 if not opts.get('print'):
482 opener = util.opener(os.path.dirname(local))
483 out = opener(os.path.basename(local), "w", atomictemp=True)
484 else:
485 out = sys.stdout
486
487 reprocess = not opts.get('no_minimal')
488
489 m3 = Merge3Text(basetext, localtext, othertext)
490 for line in m3.merge_lines(name_a=name_a, name_b=name_b,
491 reprocess=reprocess):
492 out.write(line)
493
494 if not opts.get('print'):
495 out.rename()
496
497 if m3.conflicts:
498 if not opts.get('quiet'):
499 warn(_("warning: conflicts during merge.\n"))
500 return 1
501 9
502 options = [('L', 'label', [], _('labels to use on conflict markers')), 10 options = [('L', 'label', [], _('labels to use on conflict markers')),
503 ('a', 'text', None, _('treat all files as text')), 11 ('a', 'text', None, _('treat all files as text')),
504 ('p', 'print', None, 12 ('p', 'print', None,
505 _('print results instead of overwriting LOCAL')), 13 _('print results instead of overwriting LOCAL')),
515 Apply to LOCAL the changes necessary to go from BASE to OTHER. 23 Apply to LOCAL the changes necessary to go from BASE to OTHER.
516 24
517 By default, LOCAL is overwritten with the results of this operation. 25 By default, LOCAL is overwritten with the results of this operation.
518 ''') 26 ''')
519 27
28 class ParseError(Exception):
29 """Exception raised on errors in parsing the command line."""
30
520 def showhelp(): 31 def showhelp():
521 sys.stdout.write(usage) 32 sys.stdout.write(usage)
522 sys.stdout.write('\noptions:\n') 33 sys.stdout.write('\noptions:\n')
523 34
524 out_opts = [] 35 out_opts = []
528 '%s' % desc)) 39 '%s' % desc))
529 opts_len = max([len(opt[0]) for opt in out_opts]) 40 opts_len = max([len(opt[0]) for opt in out_opts])
530 for first, second in out_opts: 41 for first, second in out_opts:
531 sys.stdout.write(' %-*s %s\n' % (opts_len, first, second)) 42 sys.stdout.write(' %-*s %s\n' % (opts_len, first, second))
532 43
533 class ParseError(Exception): 44 try:
534 """Exception raised on errors in parsing the command line.""" 45 opts = {}
535
536 def main(argv):
537 try: 46 try:
538 opts = {} 47 args = fancyopts.fancyopts(sys.argv[1:], options, opts)
539 try: 48 except fancyopts.getopt.GetoptError, e:
540 args = fancyopts.fancyopts(argv[1:], options, opts) 49 raise ParseError(e)
541 except fancyopts.getopt.GetoptError, e: 50 if opts['help']:
542 raise ParseError(e)
543 if opts['help']:
544 showhelp()
545 return 0
546 if len(args) != 3:
547 raise ParseError(_('wrong number of arguments'))
548 return simplemerge(*args, **opts)
549 except ParseError, e:
550 sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
551 showhelp() 51 showhelp()
552 return 1 52 sys.exit(0)
553 except util.Abort, e: 53 if len(args) != 3:
554 sys.stderr.write("abort: %s\n" % e) 54 raise ParseError(_('wrong number of arguments'))
555 return 255 55 sys.exit(simplemerge.simplemerge(*args, **opts))
556 except KeyboardInterrupt: 56 except ParseError, e:
557 return 255 57 sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
558 58 showhelp()
559 if __name__ == '__main__': 59 sys.exit(1)
560 import sys 60 except util.Abort, e:
561 import os 61 sys.stderr.write("abort: %s\n" % e)
562 sys.exit(main(sys.argv)) 62 sys.exit(255)
63 except KeyboardInterrupt:
64 sys.exit(255)