1
# Copyright (C) 2004, 2005 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
import bzrlib.patiencediff
24
from bzrlib.textfile import check_text_lines
27
def intersect(ra, rb):
28
"""Given two ranges return the range where they intersect or None.
30
>>> intersect((0, 10), (0, 6))
32
>>> intersect((0, 10), (5, 15))
34
>>> intersect((0, 10), (10, 15))
35
>>> intersect((0, 9), (10, 15))
36
>>> intersect((0, 9), (7, 15))
42
sa = max(ra[0], rb[0])
43
sb = min(ra[1], rb[1])
50
def compare_range(a, astart, aend, b, bstart, bend):
51
"""Compare a[astart:aend] == b[bstart:bend], without slicing.
53
if (aend-astart) != (bend-bstart):
55
for ia, ib in zip(xrange(astart, aend), xrange(bstart, bend)):
65
"""3-way merge of texts.
67
Given BASE, OTHER, THIS, tries to produce a combined text
68
incorporating the changes from both BASE->OTHER and BASE->THIS.
69
All three will typically be sequences of lines."""
70
def __init__(self, base, a, b):
71
check_text_lines(base)
84
start_marker='<<<<<<<',
89
"""Return merge in cvs-like form.
91
if base_marker and reprocess:
92
raise CantReprocessAndShowBase()
94
start_marker = start_marker + ' ' + name_a
96
end_marker = end_marker + ' ' + name_b
97
if name_base and base_marker:
98
base_marker = base_marker + ' ' + name_base
99
merge_regions = self.merge_regions()
100
if reprocess is True:
101
merge_regions = self.reprocess_merge_regions(merge_regions)
102
for t in merge_regions:
104
if what == 'unchanged':
105
for i in range(t[1], t[2]):
107
elif what == 'a' or what == 'same':
108
for i in range(t[1], t[2]):
111
for i in range(t[1], t[2]):
113
elif what == 'conflict':
114
yield start_marker + '\n'
115
for i in range(t[3], t[4]):
117
if base_marker is not None:
118
yield base_marker + '\n'
119
for i in range(t[1], t[2]):
121
yield mid_marker + '\n'
122
for i in range(t[5], t[6]):
124
yield end_marker + '\n'
126
raise ValueError(what)
132
def merge_annotated(self):
133
"""Return merge with conflicts, showing origin of lines.
135
Most useful for debugging merge.
137
for t in self.merge_regions():
139
if what == 'unchanged':
140
for i in range(t[1], t[2]):
141
yield 'u | ' + self.base[i]
142
elif what == 'a' or what == 'same':
143
for i in range(t[1], t[2]):
144
yield what[0] + ' | ' + self.a[i]
146
for i in range(t[1], t[2]):
147
yield 'b | ' + self.b[i]
148
elif what == 'conflict':
150
for i in range(t[3], t[4]):
151
yield 'A | ' + self.a[i]
153
for i in range(t[5], t[6]):
154
yield 'B | ' + self.b[i]
157
raise ValueError(what)
163
def merge_groups(self):
164
"""Yield sequence of line groups. Each one is a tuple:
167
Lines unchanged from base
173
Lines taken from a (and equal to b)
178
'conflict', base_lines, a_lines, b_lines
179
Lines from base were changed to either a or b and conflict.
181
for t in self.merge_regions():
183
if what == 'unchanged':
184
yield what, self.base[t[1]:t[2]]
185
elif what == 'a' or what == 'same':
186
yield what, self.a[t[1]:t[2]]
188
yield what, self.b[t[1]:t[2]]
189
elif what == 'conflict':
191
self.base[t[1]:t[2]],
195
raise ValueError(what)
198
def merge_regions(self):
199
"""Return sequences of matching and conflicting regions.
201
This returns tuples, where the first value says what kind we
204
'unchanged', start, end
205
Take a region of base[start:end]
208
b and a are different from base but give the same result
211
Non-clashing insertion from a[start:end]
213
Method is as follows:
215
The two sequences align only on regions which match the base
216
and both descendents. These are found by doing a two-way diff
217
of each one against the base, and then finding the
218
intersections between those regions. These "sync regions"
219
are by definition unchanged in both and easily dealt with.
221
The regions in between can be in any of three cases:
222
conflicted, or changed on only one side.
225
# section a[0:ia] has been disposed of, etc
228
for zmatch, zend, amatch, aend, bmatch, bend in self.find_sync_regions():
229
#print 'match base [%d:%d]' % (zmatch, zend)
231
matchlen = zend - zmatch
233
assert matchlen == (aend - amatch)
234
assert matchlen == (bend - bmatch)
238
len_base = zmatch - iz
243
#print 'unmatched a=%d, b=%d' % (len_a, len_b)
246
# try to avoid actually slicing the lists
247
equal_a = compare_range(self.a, ia, amatch,
248
self.base, iz, zmatch)
249
equal_b = compare_range(self.b, ib, bmatch,
250
self.base, iz, zmatch)
251
same = compare_range(self.a, ia, amatch,
255
yield 'same', ia, amatch
256
elif equal_a and not equal_b:
257
yield 'b', ib, bmatch
258
elif equal_b and not equal_a:
259
yield 'a', ia, amatch
260
elif not equal_a and not equal_b:
261
yield 'conflict', iz, zmatch, ia, amatch, ib, bmatch
263
raise AssertionError("can't handle a=b=base but unmatched")
269
# if the same part of the base was deleted on both sides
270
# that's OK, we can just skip it.
278
yield 'unchanged', zmatch, zend
284
def reprocess_merge_regions(self, merge_regions):
285
"""Where there are conflict regions, remove the agreed lines.
287
Lines where both A and B have made the same changes are
290
for region in merge_regions:
291
if region[0] != "conflict":
294
type, iz, zmatch, ia, amatch, ib, bmatch = region
295
a_region = self.a[ia:amatch]
296
b_region = self.b[ib:bmatch]
297
matches = bzrlib.patiencediff.PatienceSequenceMatcher(
298
None, a_region, b_region).get_matching_blocks()
301
for region_ia, region_ib, region_len in matches[:-1]:
304
reg = self.mismatch_region(next_a, region_ia, next_b,
308
yield 'same', region_ia, region_len+region_ia
309
next_a = region_ia + region_len
310
next_b = region_ib + region_len
311
reg = self.mismatch_region(next_a, amatch, next_b, bmatch)
317
def mismatch_region(next_a, region_ia, next_b, region_ib):
318
if next_a < region_ia or next_b < region_ib:
319
return 'conflict', None, None, next_a, region_ia, next_b, region_ib
322
def find_sync_regions(self):
323
"""Return a list of sync regions, where both descendents match the base.
325
Generates a list of (base1, base2, a1, a2, b1, b2). There is
326
always a zero-length sync region at the end of all the files.
330
amatches = bzrlib.patiencediff.PatienceSequenceMatcher(
331
None, self.base, self.a).get_matching_blocks()
332
bmatches = bzrlib.patiencediff.PatienceSequenceMatcher(
333
None, self.base, self.b).get_matching_blocks()
334
len_a = len(amatches)
335
len_b = len(bmatches)
339
while ia < len_a and ib < len_b:
340
abase, amatch, alen = amatches[ia]
341
bbase, bmatch, blen = bmatches[ib]
343
# there is an unconflicted block at i; how long does it
344
# extend? until whichever one ends earlier.
345
i = intersect((abase, abase+alen), (bbase, bbase+blen))
349
intlen = intend - intbase
351
# found a match of base[i[0], i[1]]; this may be less than
352
# the region that matches in either one
353
assert intlen <= alen
354
assert intlen <= blen
355
assert abase <= intbase
356
assert bbase <= intbase
358
asub = amatch + (intbase - abase)
359
bsub = bmatch + (intbase - bbase)
363
assert self.base[intbase:intend] == self.a[asub:aend], \
364
(self.base[intbase:intend], self.a[asub:aend])
366
assert self.base[intbase:intend] == self.b[bsub:bend]
368
sl.append((intbase, intend,
372
# advance whichever one ends first in the base text
373
if (abase + alen) < (bbase + blen):
378
intbase = len(self.base)
381
sl.append((intbase, intbase, abase, abase, bbase, bbase))
387
def find_unconflicted(self):
388
"""Return a list of ranges in base that are not conflicted."""
389
am = bzrlib.patiencediff.PatienceSequenceMatcher(
390
None, self.base, self.a).get_matching_blocks()
391
bm = bzrlib.patiencediff.PatienceSequenceMatcher(
392
None, self.base, self.b).get_matching_blocks()
397
# there is an unconflicted block at i; how long does it
398
# extend? until whichever one ends earlier.
403
i = intersect((a1, a2), (b1, b2))
416
# as for diff3 and meld the syntax is "MINE BASE OTHER"
417
a = file(argv[1], 'rt').readlines()
418
base = file(argv[2], 'rt').readlines()
419
b = file(argv[3], 'rt').readlines()
421
m3 = Merge3(base, a, b)
423
#for sr in m3.find_sync_regions():
426
# sys.stdout.writelines(m3.merge_lines(name_a=argv[1], name_b=argv[3]))
427
sys.stdout.writelines(m3.merge_annotated())
430
if __name__ == '__main__':
432
sys.exit(main(sys.argv))