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.
 
 
93
            if self.a[0].endswith('\r\n'):
 
 
95
            elif self.a[0].endswith('\r'):
 
 
97
        if base_marker and reprocess:
 
 
98
            raise CantReprocessAndShowBase()
 
 
100
            start_marker = start_marker + ' ' + name_a
 
 
102
            end_marker = end_marker + ' ' + name_b
 
 
103
        if name_base and base_marker:
 
 
104
            base_marker = base_marker + ' ' + name_base
 
 
105
        merge_regions = self.merge_regions()
 
 
106
        if reprocess is True:
 
 
107
            merge_regions = self.reprocess_merge_regions(merge_regions)
 
 
108
        for t in merge_regions:
 
 
110
            if what == 'unchanged':
 
 
111
                for i in range(t[1], t[2]):
 
 
113
            elif what == 'a' or what == 'same':
 
 
114
                for i in range(t[1], t[2]):
 
 
117
                for i in range(t[1], t[2]):
 
 
119
            elif what == 'conflict':
 
 
120
                yield start_marker + newline
 
 
121
                for i in range(t[3], t[4]):
 
 
123
                if base_marker is not None:
 
 
124
                    yield base_marker + newline
 
 
125
                    for i in range(t[1], t[2]):
 
 
127
                yield mid_marker + newline
 
 
128
                for i in range(t[5], t[6]):
 
 
130
                yield end_marker + newline
 
 
132
                raise ValueError(what)
 
 
138
    def merge_annotated(self):
 
 
139
        """Return merge with conflicts, showing origin of lines.
 
 
141
        Most useful for debugging merge.        
 
 
143
        for t in self.merge_regions():
 
 
145
            if what == 'unchanged':
 
 
146
                for i in range(t[1], t[2]):
 
 
147
                    yield 'u | ' + self.base[i]
 
 
148
            elif what == 'a' or what == 'same':
 
 
149
                for i in range(t[1], t[2]):
 
 
150
                    yield what[0] + ' | ' + self.a[i]
 
 
152
                for i in range(t[1], t[2]):
 
 
153
                    yield 'b | ' + self.b[i]
 
 
154
            elif what == 'conflict':
 
 
156
                for i in range(t[3], t[4]):
 
 
157
                    yield 'A | ' + self.a[i]
 
 
159
                for i in range(t[5], t[6]):
 
 
160
                    yield 'B | ' + self.b[i]
 
 
163
                raise ValueError(what)
 
 
169
    def merge_groups(self):
 
 
170
        """Yield sequence of line groups.  Each one is a tuple:
 
 
173
             Lines unchanged from base
 
 
179
             Lines taken from a (and equal to b)
 
 
184
        'conflict', base_lines, a_lines, b_lines
 
 
185
             Lines from base were changed to either a or b and conflict.
 
 
187
        for t in self.merge_regions():
 
 
189
            if what == 'unchanged':
 
 
190
                yield what, self.base[t[1]:t[2]]
 
 
191
            elif what == 'a' or what == 'same':
 
 
192
                yield what, self.a[t[1]:t[2]]
 
 
194
                yield what, self.b[t[1]:t[2]]
 
 
195
            elif what == 'conflict':
 
 
197
                       self.base[t[1]:t[2]],
 
 
201
                raise ValueError(what)
 
 
204
    def merge_regions(self):
 
 
205
        """Return sequences of matching and conflicting regions.
 
 
207
        This returns tuples, where the first value says what kind we
 
 
210
        'unchanged', start, end
 
 
211
             Take a region of base[start:end]
 
 
214
             b and a are different from base but give the same result
 
 
217
             Non-clashing insertion from a[start:end]
 
 
219
        Method is as follows:
 
 
221
        The two sequences align only on regions which match the base
 
 
222
        and both descendents.  These are found by doing a two-way diff
 
 
223
        of each one against the base, and then finding the
 
 
224
        intersections between those regions.  These "sync regions"
 
 
225
        are by definition unchanged in both and easily dealt with.
 
 
227
        The regions in between can be in any of three cases:
 
 
228
        conflicted, or changed on only one side.
 
 
231
        # section a[0:ia] has been disposed of, etc
 
 
234
        for zmatch, zend, amatch, aend, bmatch, bend in self.find_sync_regions():
 
 
235
            #print 'match base [%d:%d]' % (zmatch, zend)
 
 
237
            matchlen = zend - zmatch
 
 
239
            assert matchlen == (aend - amatch)
 
 
240
            assert matchlen == (bend - bmatch)
 
 
244
            len_base = zmatch - iz
 
 
249
            #print 'unmatched a=%d, b=%d' % (len_a, len_b)
 
 
252
                # try to avoid actually slicing the lists
 
 
253
                equal_a = compare_range(self.a, ia, amatch,
 
 
254
                                        self.base, iz, zmatch)
 
 
255
                equal_b = compare_range(self.b, ib, bmatch,
 
 
256
                                        self.base, iz, zmatch)
 
 
257
                same = compare_range(self.a, ia, amatch,
 
 
261
                    yield 'same', ia, amatch
 
 
262
                elif equal_a and not equal_b:
 
 
263
                    yield 'b', ib, bmatch
 
 
264
                elif equal_b and not equal_a:
 
 
265
                    yield 'a', ia, amatch
 
 
266
                elif not equal_a and not equal_b:
 
 
267
                    yield 'conflict', iz, zmatch, ia, amatch, ib, bmatch
 
 
269
                    raise AssertionError("can't handle a=b=base but unmatched")
 
 
275
            # if the same part of the base was deleted on both sides
 
 
276
            # that's OK, we can just skip it.
 
 
284
                yield 'unchanged', zmatch, zend
 
 
290
    def reprocess_merge_regions(self, merge_regions):
 
 
291
        """Where there are conflict regions, remove the agreed lines.
 
 
293
        Lines where both A and B have made the same changes are 
 
 
296
        for region in merge_regions:
 
 
297
            if region[0] != "conflict":
 
 
300
            type, iz, zmatch, ia, amatch, ib, bmatch = region
 
 
301
            a_region = self.a[ia:amatch]
 
 
302
            b_region = self.b[ib:bmatch]
 
 
303
            matches = bzrlib.patiencediff.PatienceSequenceMatcher(
 
 
304
                    None, a_region, b_region).get_matching_blocks()
 
 
307
            for region_ia, region_ib, region_len in matches[:-1]:
 
 
310
                reg = self.mismatch_region(next_a, region_ia, next_b,
 
 
314
                yield 'same', region_ia, region_len+region_ia
 
 
315
                next_a = region_ia + region_len
 
 
316
                next_b = region_ib + region_len
 
 
317
            reg = self.mismatch_region(next_a, amatch, next_b, bmatch)
 
 
323
    def mismatch_region(next_a, region_ia,  next_b, region_ib):
 
 
324
        if next_a < region_ia or next_b < region_ib:
 
 
325
            return 'conflict', None, None, next_a, region_ia, next_b, region_ib
 
 
328
    def find_sync_regions(self):
 
 
329
        """Return a list of sync regions, where both descendents match the base.
 
 
331
        Generates a list of (base1, base2, a1, a2, b1, b2).  There is
 
 
332
        always a zero-length sync region at the end of all the files.
 
 
336
        amatches = bzrlib.patiencediff.PatienceSequenceMatcher(
 
 
337
                None, self.base, self.a).get_matching_blocks()
 
 
338
        bmatches = bzrlib.patiencediff.PatienceSequenceMatcher(
 
 
339
                None, self.base, self.b).get_matching_blocks()
 
 
340
        len_a = len(amatches)
 
 
341
        len_b = len(bmatches)
 
 
345
        while ia < len_a and ib < len_b:
 
 
346
            abase, amatch, alen = amatches[ia]
 
 
347
            bbase, bmatch, blen = bmatches[ib]
 
 
349
            # there is an unconflicted block at i; how long does it
 
 
350
            # extend?  until whichever one ends earlier.
 
 
351
            i = intersect((abase, abase+alen), (bbase, bbase+blen))
 
 
355
                intlen = intend - intbase
 
 
357
                # found a match of base[i[0], i[1]]; this may be less than
 
 
358
                # the region that matches in either one
 
 
359
                assert intlen <= alen
 
 
360
                assert intlen <= blen
 
 
361
                assert abase <= intbase
 
 
362
                assert bbase <= intbase
 
 
364
                asub = amatch + (intbase - abase)
 
 
365
                bsub = bmatch + (intbase - bbase)
 
 
369
                assert self.base[intbase:intend] == self.a[asub:aend], \
 
 
370
                       (self.base[intbase:intend], self.a[asub:aend])
 
 
372
                assert self.base[intbase:intend] == self.b[bsub:bend]
 
 
374
                sl.append((intbase, intend,
 
 
378
            # advance whichever one ends first in the base text
 
 
379
            if (abase + alen) < (bbase + blen):
 
 
384
        intbase = len(self.base)
 
 
387
        sl.append((intbase, intbase, abase, abase, bbase, bbase))
 
 
393
    def find_unconflicted(self):
 
 
394
        """Return a list of ranges in base that are not conflicted."""
 
 
395
        am = bzrlib.patiencediff.PatienceSequenceMatcher(
 
 
396
                None, self.base, self.a).get_matching_blocks()
 
 
397
        bm = bzrlib.patiencediff.PatienceSequenceMatcher(
 
 
398
                None, self.base, self.b).get_matching_blocks()
 
 
403
            # there is an unconflicted block at i; how long does it
 
 
404
            # extend?  until whichever one ends earlier.
 
 
409
            i = intersect((a1, a2), (b1, b2))
 
 
422
    # as for diff3 and meld the syntax is "MINE BASE OTHER"
 
 
423
    a = file(argv[1], 'rt').readlines()
 
 
424
    base = file(argv[2], 'rt').readlines()
 
 
425
    b = file(argv[3], 'rt').readlines()
 
 
427
    m3 = Merge3(base, a, b)
 
 
429
    #for sr in m3.find_sync_regions():
 
 
432
    # sys.stdout.writelines(m3.merge_lines(name_a=argv[1], name_b=argv[3]))
 
 
433
    sys.stdout.writelines(m3.merge_annotated())
 
 
436
if __name__ == '__main__':
 
 
438
    sys.exit(main(sys.argv))