1
# Copyright (C) 2004, 2005 by Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
# mbp: "you know that thing where cvs gives you conflict markers?"
22
from bzrlib.errors import CantReprocessAndShowBase
23
from bzrlib.patiencediff import SequenceMatcher
24
from bzrlib.textfile import check_text_lines
26
def intersect(ra, rb):
27
"""Given two ranges return the range where they intersect or None.
29
>>> intersect((0, 10), (0, 6))
31
>>> intersect((0, 10), (5, 15))
33
>>> intersect((0, 10), (10, 15))
34
>>> intersect((0, 9), (10, 15))
35
>>> intersect((0, 9), (7, 15))
41
sa = max(ra[0], rb[0])
42
sb = min(ra[1], rb[1])
49
def compare_range(a, astart, aend, b, bstart, bend):
50
"""Compare a[astart:aend] == b[bstart:bend], without slicing.
52
if (aend-astart) != (bend-bstart):
54
for ia, ib in zip(xrange(astart, aend), xrange(bstart, bend)):
64
"""3-way merge of texts.
66
Given BASE, OTHER, THIS, tries to produce a combined text
67
incorporating the changes from both BASE->OTHER and BASE->THIS.
68
All three will typically be sequences of lines."""
69
def __init__(self, base, a, b):
70
check_text_lines(base)
83
start_marker='<<<<<<<',
88
"""Return merge in cvs-like form.
90
if base_marker and reprocess:
91
raise CantReprocessAndShowBase()
93
start_marker = start_marker + ' ' + name_a
95
end_marker = end_marker + ' ' + name_b
96
if name_base and base_marker:
97
base_marker = base_marker + ' ' + name_base
98
merge_regions = self.merge_regions()
100
merge_regions = self.reprocess_merge_regions(merge_regions)
101
for t in merge_regions:
103
if what == 'unchanged':
104
for i in range(t[1], t[2]):
106
elif what == 'a' or what == 'same':
107
for i in range(t[1], t[2]):
110
for i in range(t[1], t[2]):
112
elif what == 'conflict':
113
yield start_marker + '\n'
114
for i in range(t[3], t[4]):
116
if base_marker is not None:
117
yield base_marker + '\n'
118
for i in range(t[1], t[2]):
120
yield mid_marker + '\n'
121
for i in range(t[5], t[6]):
123
yield end_marker + '\n'
125
raise ValueError(what)
131
def merge_annotated(self):
132
"""Return merge with conflicts, showing origin of lines.
134
Most useful for debugging merge.
136
for t in self.merge_regions():
138
if what == 'unchanged':
139
for i in range(t[1], t[2]):
140
yield 'u | ' + self.base[i]
141
elif what == 'a' or what == 'same':
142
for i in range(t[1], t[2]):
143
yield what[0] + ' | ' + self.a[i]
145
for i in range(t[1], t[2]):
146
yield 'b | ' + self.b[i]
147
elif what == 'conflict':
149
for i in range(t[3], t[4]):
150
yield 'A | ' + self.a[i]
152
for i in range(t[5], t[6]):
153
yield 'B | ' + self.b[i]
156
raise ValueError(what)
162
def merge_groups(self):
163
"""Yield sequence of line groups. Each one is a tuple:
166
Lines unchanged from base
172
Lines taken from a (and equal to b)
177
'conflict', base_lines, a_lines, b_lines
178
Lines from base were changed to either a or b and conflict.
180
for t in self.merge_regions():
182
if what == 'unchanged':
183
yield what, self.base[t[1]:t[2]]
184
elif what == 'a' or what == 'same':
185
yield what, self.a[t[1]:t[2]]
187
yield what, self.b[t[1]:t[2]]
188
elif what == 'conflict':
190
self.base[t[1]:t[2]],
194
raise ValueError(what)
197
def merge_regions(self):
198
"""Return sequences of matching and conflicting regions.
200
This returns tuples, where the first value says what kind we
203
'unchanged', start, end
204
Take a region of base[start:end]
207
b and a are different from base but give the same result
210
Non-clashing insertion from a[start:end]
212
Method is as follows:
214
The two sequences align only on regions which match the base
215
and both descendents. These are found by doing a two-way diff
216
of each one against the base, and then finding the
217
intersections between those regions. These "sync regions"
218
are by definition unchanged in both and easily dealt with.
220
The regions in between can be in any of three cases:
221
conflicted, or changed on only one side.
224
# section a[0:ia] has been disposed of, etc
227
for zmatch, zend, amatch, aend, bmatch, bend in self.find_sync_regions():
228
#print 'match base [%d:%d]' % (zmatch, zend)
230
matchlen = zend - zmatch
232
assert matchlen == (aend - amatch)
233
assert matchlen == (bend - bmatch)
237
len_base = zmatch - iz
242
#print 'unmatched a=%d, b=%d' % (len_a, len_b)
245
# try to avoid actually slicing the lists
246
equal_a = compare_range(self.a, ia, amatch,
247
self.base, iz, zmatch)
248
equal_b = compare_range(self.b, ib, bmatch,
249
self.base, iz, zmatch)
250
same = compare_range(self.a, ia, amatch,
254
yield 'same', ia, amatch
255
elif equal_a and not equal_b:
256
yield 'b', ib, bmatch
257
elif equal_b and not equal_a:
258
yield 'a', ia, amatch
259
elif not equal_a and not equal_b:
260
yield 'conflict', iz, zmatch, ia, amatch, ib, bmatch
262
raise AssertionError("can't handle a=b=base but unmatched")
268
# if the same part of the base was deleted on both sides
269
# that's OK, we can just skip it.
277
yield 'unchanged', zmatch, zend
283
def reprocess_merge_regions(self, merge_regions):
284
"""Where there are conflict regions, remove the agreed lines.
286
Lines where both A and B have made the same changes are
289
for region in merge_regions:
290
if region[0] != "conflict":
293
type, iz, zmatch, ia, amatch, ib, bmatch = region
294
a_region = self.a[ia:amatch]
295
b_region = self.b[ib:bmatch]
296
matches = SequenceMatcher(None, a_region,
297
b_region).get_matching_blocks()
300
for region_ia, region_ib, region_len in matches[:-1]:
303
reg = self.mismatch_region(next_a, region_ia, next_b,
307
yield 'same', region_ia, region_len+region_ia
308
next_a = region_ia + region_len
309
next_b = region_ib + region_len
310
reg = self.mismatch_region(next_a, amatch, next_b, bmatch)
316
def mismatch_region(next_a, region_ia, next_b, region_ib):
317
if next_a < region_ia or next_b < region_ib:
318
return 'conflict', None, None, next_a, region_ia, next_b, region_ib
321
def find_sync_regions(self):
322
"""Return a list of sync regions, where both descendents match the base.
324
Generates a list of (base1, base2, a1, a2, b1, b2). There is
325
always a zero-length sync region at the end of all the files.
329
amatches = SequenceMatcher(None, self.base, self.a).get_matching_blocks()
330
bmatches = SequenceMatcher(None, self.base, self.b).get_matching_blocks()
331
len_a = len(amatches)
332
len_b = len(bmatches)
336
while ia < len_a and ib < len_b:
337
abase, amatch, alen = amatches[ia]
338
bbase, bmatch, blen = bmatches[ib]
340
# there is an unconflicted block at i; how long does it
341
# extend? until whichever one ends earlier.
342
i = intersect((abase, abase+alen), (bbase, bbase+blen))
346
intlen = intend - intbase
348
# found a match of base[i[0], i[1]]; this may be less than
349
# the region that matches in either one
350
assert intlen <= alen
351
assert intlen <= blen
352
assert abase <= intbase
353
assert bbase <= intbase
355
asub = amatch + (intbase - abase)
356
bsub = bmatch + (intbase - bbase)
360
assert self.base[intbase:intend] == self.a[asub:aend], \
361
(self.base[intbase:intend], self.a[asub:aend])
363
assert self.base[intbase:intend] == self.b[bsub:bend]
365
sl.append((intbase, intend,
369
# advance whichever one ends first in the base text
370
if (abase + alen) < (bbase + blen):
375
intbase = len(self.base)
378
sl.append((intbase, intbase, abase, abase, bbase, bbase))
384
def find_unconflicted(self):
385
"""Return a list of ranges in base that are not conflicted."""
386
am = SequenceMatcher(None, self.base, self.a).get_matching_blocks()
387
bm = SequenceMatcher(None, self.base, self.b).get_matching_blocks()
392
# there is an unconflicted block at i; how long does it
393
# extend? until whichever one ends earlier.
398
i = intersect((a1, a2), (b1, b2))
411
# as for diff3 and meld the syntax is "MINE BASE OTHER"
412
a = file(argv[1], 'rt').readlines()
413
base = file(argv[2], 'rt').readlines()
414
b = file(argv[3], 'rt').readlines()
416
m3 = Merge3(base, a, b)
418
#for sr in m3.find_sync_regions():
421
# sys.stdout.writelines(m3.merge_lines(name_a=argv[1], name_b=argv[3]))
422
sys.stdout.writelines(m3.merge_annotated())
425
if __name__ == '__main__':
427
sys.exit(main(sys.argv))