/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/merge3.py

More work on roundtrip push support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005 Canonical Ltd
2
 
#
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.
7
 
#
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.
12
 
#
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
16
 
 
17
 
 
18
 
# mbp: "you know that thing where cvs gives you conflict markers?"
19
 
# s: "i hate that."
20
 
 
21
 
 
22
 
from bzrlib.errors import CantReprocessAndShowBase
23
 
import bzrlib.patiencediff
24
 
from bzrlib.textfile import check_text_lines
25
 
 
26
 
 
27
 
def intersect(ra, rb):
28
 
    """Given two ranges return the range where they intersect or None.
29
 
 
30
 
    >>> intersect((0, 10), (0, 6))
31
 
    (0, 6)
32
 
    >>> intersect((0, 10), (5, 15))
33
 
    (5, 10)
34
 
    >>> intersect((0, 10), (10, 15))
35
 
    >>> intersect((0, 9), (10, 15))
36
 
    >>> intersect((0, 9), (7, 15))
37
 
    (7, 9)
38
 
    """
39
 
    assert ra[0] <= ra[1]
40
 
    assert rb[0] <= rb[1]
41
 
    
42
 
    sa = max(ra[0], rb[0])
43
 
    sb = min(ra[1], rb[1])
44
 
    if sa < sb:
45
 
        return sa, sb
46
 
    else:
47
 
        return None
48
 
 
49
 
 
50
 
def compare_range(a, astart, aend, b, bstart, bend):
51
 
    """Compare a[astart:aend] == b[bstart:bend], without slicing.
52
 
    """
53
 
    if (aend-astart) != (bend-bstart):
54
 
        return False
55
 
    for ia, ib in zip(xrange(astart, aend), xrange(bstart, bend)):
56
 
        if a[ia] != b[ib]:
57
 
            return False
58
 
    else:
59
 
        return True
60
 
        
61
 
 
62
 
 
63
 
 
64
 
class Merge3(object):
65
 
    """3-way merge of texts.
66
 
 
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, is_cherrypick=False):
71
 
        check_text_lines(base)
72
 
        check_text_lines(a)
73
 
        check_text_lines(b)
74
 
        self.base = base
75
 
        self.a = a
76
 
        self.b = b
77
 
        self.is_cherrypick = is_cherrypick
78
 
 
79
 
    def merge_lines(self,
80
 
                    name_a=None,
81
 
                    name_b=None,
82
 
                    name_base=None,
83
 
                    start_marker='<<<<<<<',
84
 
                    mid_marker='=======',
85
 
                    end_marker='>>>>>>>',
86
 
                    base_marker=None,
87
 
                    reprocess=False):
88
 
        """Return merge in cvs-like form.
89
 
        """
90
 
        newline = '\n'
91
 
        if len(self.a) > 0:
92
 
            if self.a[0].endswith('\r\n'):
93
 
                newline = '\r\n'
94
 
            elif self.a[0].endswith('\r'):
95
 
                newline = '\r'
96
 
        if base_marker and reprocess:
97
 
            raise CantReprocessAndShowBase()
98
 
        if name_a:
99
 
            start_marker = start_marker + ' ' + name_a
100
 
        if name_b:
101
 
            end_marker = end_marker + ' ' + name_b
102
 
        if name_base and base_marker:
103
 
            base_marker = base_marker + ' ' + name_base
104
 
        merge_regions = self.merge_regions()
105
 
        if reprocess is True:
106
 
            merge_regions = self.reprocess_merge_regions(merge_regions)
107
 
        for t in merge_regions:
108
 
            what = t[0]
109
 
            if what == 'unchanged':
110
 
                for i in range(t[1], t[2]):
111
 
                    yield self.base[i]
112
 
            elif what == 'a' or what == 'same':
113
 
                for i in range(t[1], t[2]):
114
 
                    yield self.a[i]
115
 
            elif what == 'b':
116
 
                for i in range(t[1], t[2]):
117
 
                    yield self.b[i]
118
 
            elif what == 'conflict':
119
 
                yield start_marker + newline
120
 
                for i in range(t[3], t[4]):
121
 
                    yield self.a[i]
122
 
                if base_marker is not None:
123
 
                    yield base_marker + newline
124
 
                    for i in range(t[1], t[2]):
125
 
                        yield self.base[i]
126
 
                yield mid_marker + newline
127
 
                for i in range(t[5], t[6]):
128
 
                    yield self.b[i]
129
 
                yield end_marker + newline
130
 
            else:
131
 
                raise ValueError(what)
132
 
 
133
 
    def merge_annotated(self):
134
 
        """Return merge with conflicts, showing origin of lines.
135
 
 
136
 
        Most useful for debugging merge.        
137
 
        """
138
 
        for t in self.merge_regions():
139
 
            what = t[0]
140
 
            if what == 'unchanged':
141
 
                for i in range(t[1], t[2]):
142
 
                    yield 'u | ' + self.base[i]
143
 
            elif what == 'a' or what == 'same':
144
 
                for i in range(t[1], t[2]):
145
 
                    yield what[0] + ' | ' + self.a[i]
146
 
            elif what == 'b':
147
 
                for i in range(t[1], t[2]):
148
 
                    yield 'b | ' + self.b[i]
149
 
            elif what == 'conflict':
150
 
                yield '<<<<\n'
151
 
                for i in range(t[3], t[4]):
152
 
                    yield 'A | ' + self.a[i]
153
 
                yield '----\n'
154
 
                for i in range(t[5], t[6]):
155
 
                    yield 'B | ' + self.b[i]
156
 
                yield '>>>>\n'
157
 
            else:
158
 
                raise ValueError(what)
159
 
 
160
 
    def merge_groups(self):
161
 
        """Yield sequence of line groups.  Each one is a tuple:
162
 
 
163
 
        'unchanged', lines
164
 
             Lines unchanged from base
165
 
 
166
 
        'a', lines
167
 
             Lines taken from a
168
 
 
169
 
        'same', lines
170
 
             Lines taken from a (and equal to b)
171
 
 
172
 
        'b', lines
173
 
             Lines taken from b
174
 
 
175
 
        'conflict', base_lines, a_lines, b_lines
176
 
             Lines from base were changed to either a or b and conflict.
177
 
        """
178
 
        for t in self.merge_regions():
179
 
            what = t[0]
180
 
            if what == 'unchanged':
181
 
                yield what, self.base[t[1]:t[2]]
182
 
            elif what == 'a' or what == 'same':
183
 
                yield what, self.a[t[1]:t[2]]
184
 
            elif what == 'b':
185
 
                yield what, self.b[t[1]:t[2]]
186
 
            elif what == 'conflict':
187
 
                yield (what,
188
 
                       self.base[t[1]:t[2]],
189
 
                       self.a[t[3]:t[4]],
190
 
                       self.b[t[5]:t[6]])
191
 
            else:
192
 
                raise ValueError(what)
193
 
 
194
 
    def merge_regions(self):
195
 
        """Return sequences of matching and conflicting regions.
196
 
 
197
 
        This returns tuples, where the first value says what kind we
198
 
        have:
199
 
 
200
 
        'unchanged', start, end
201
 
             Take a region of base[start:end]
202
 
 
203
 
        'same', astart, aend
204
 
             b and a are different from base but give the same result
205
 
 
206
 
        'a', start, end
207
 
             Non-clashing insertion from a[start:end]
208
 
 
209
 
        Method is as follows:
210
 
 
211
 
        The two sequences align only on regions which match the base
212
 
        and both descendents.  These are found by doing a two-way diff
213
 
        of each one against the base, and then finding the
214
 
        intersections between those regions.  These "sync regions"
215
 
        are by definition unchanged in both and easily dealt with.
216
 
 
217
 
        The regions in between can be in any of three cases:
218
 
        conflicted, or changed on only one side.
219
 
        """
220
 
 
221
 
        # section a[0:ia] has been disposed of, etc
222
 
        iz = ia = ib = 0
223
 
        
224
 
        for zmatch, zend, amatch, aend, bmatch, bend in self.find_sync_regions():
225
 
            #print 'match base [%d:%d]' % (zmatch, zend)
226
 
            
227
 
            matchlen = zend - zmatch
228
 
            assert matchlen >= 0
229
 
            assert matchlen == (aend - amatch)
230
 
            assert matchlen == (bend - bmatch)
231
 
            
232
 
            len_a = amatch - ia
233
 
            len_b = bmatch - ib
234
 
            len_base = zmatch - iz
235
 
            assert len_a >= 0
236
 
            assert len_b >= 0
237
 
            assert len_base >= 0
238
 
 
239
 
            #print 'unmatched a=%d, b=%d' % (len_a, len_b)
240
 
 
241
 
            if len_a or len_b:
242
 
                # try to avoid actually slicing the lists
243
 
                same = compare_range(self.a, ia, amatch,
244
 
                                     self.b, ib, bmatch)
245
 
 
246
 
                if same:
247
 
                    yield 'same', ia, amatch
248
 
                else:
249
 
                    equal_a = compare_range(self.a, ia, amatch,
250
 
                                            self.base, iz, zmatch)
251
 
                    equal_b = compare_range(self.b, ib, bmatch,
252
 
                                            self.base, iz, zmatch)
253
 
                    if equal_a and not equal_b:
254
 
                        yield 'b', ib, bmatch
255
 
                    elif equal_b and not equal_a:
256
 
                        yield 'a', ia, amatch
257
 
                    elif not equal_a and not equal_b:
258
 
                        if self.is_cherrypick:
259
 
                            for node in self._refine_cherrypick_conflict(
260
 
                                                    iz, zmatch, ia, amatch,
261
 
                                                    ib, bmatch):
262
 
                                yield node
263
 
                        else:
264
 
                            yield 'conflict', iz, zmatch, ia, amatch, ib, bmatch
265
 
                    else:
266
 
                        raise AssertionError("can't handle a=b=base but unmatched")
267
 
 
268
 
                ia = amatch
269
 
                ib = bmatch
270
 
            iz = zmatch
271
 
 
272
 
            # if the same part of the base was deleted on both sides
273
 
            # that's OK, we can just skip it.
274
 
 
275
 
            if matchlen > 0:
276
 
                assert ia == amatch
277
 
                assert ib == bmatch
278
 
                assert iz == zmatch
279
 
                
280
 
                yield 'unchanged', zmatch, zend
281
 
                iz = zend
282
 
                ia = aend
283
 
                ib = bend
284
 
 
285
 
    def _refine_cherrypick_conflict(self, zstart, zend, astart, aend, bstart, bend):
286
 
        """When cherrypicking b => a, ignore matches with b and base."""
287
 
        # Do not emit regions which match, only regions which do not match
288
 
        matches = bzrlib.patiencediff.PatienceSequenceMatcher(None,
289
 
            self.base[zstart:zend], self.b[bstart:bend]).get_matching_blocks()
290
 
        last_base_idx = 0
291
 
        last_b_idx = 0
292
 
        last_b_idx = 0
293
 
        yielded_a = False
294
 
        for base_idx, b_idx, match_len in matches:
295
 
            conflict_z_len = base_idx - last_base_idx
296
 
            conflict_b_len = b_idx - last_b_idx
297
 
            if conflict_b_len == 0: # There are no lines in b which conflict,
298
 
                                    # so skip it
299
 
                pass
300
 
            else:
301
 
                if yielded_a:
302
 
                    yield ('conflict',
303
 
                           zstart + last_base_idx, zstart + base_idx,
304
 
                           aend, aend, bstart + last_b_idx, bstart + b_idx)
305
 
                else:
306
 
                    # The first conflict gets the a-range
307
 
                    yielded_a = True
308
 
                    yield ('conflict', zstart + last_base_idx, zstart +
309
 
                    base_idx,
310
 
                           astart, aend, bstart + last_b_idx, bstart + b_idx)
311
 
            last_base_idx = base_idx + match_len
312
 
            last_b_idx = b_idx + match_len
313
 
        if last_base_idx != zend - zstart or last_b_idx != bend - bstart:
314
 
            if yielded_a:
315
 
                yield ('conflict', zstart + last_base_idx, zstart + base_idx,
316
 
                       aend, aend, bstart + last_b_idx, bstart + b_idx)
317
 
            else:
318
 
                # The first conflict gets the a-range
319
 
                yielded_a = True
320
 
                yield ('conflict', zstart + last_base_idx, zstart + base_idx,
321
 
                       astart, aend, bstart + last_b_idx, bstart + b_idx)
322
 
        if not yielded_a:
323
 
            yield ('conflict', zstart, zend, astart, aend, bstart, bend)
324
 
 
325
 
    def reprocess_merge_regions(self, merge_regions):
326
 
        """Where there are conflict regions, remove the agreed lines.
327
 
 
328
 
        Lines where both A and B have made the same changes are 
329
 
        eliminated.
330
 
        """
331
 
        for region in merge_regions:
332
 
            if region[0] != "conflict":
333
 
                yield region
334
 
                continue
335
 
            type, iz, zmatch, ia, amatch, ib, bmatch = region
336
 
            a_region = self.a[ia:amatch]
337
 
            b_region = self.b[ib:bmatch]
338
 
            matches = bzrlib.patiencediff.PatienceSequenceMatcher(
339
 
                    None, a_region, b_region).get_matching_blocks()
340
 
            next_a = ia
341
 
            next_b = ib
342
 
            for region_ia, region_ib, region_len in matches[:-1]:
343
 
                region_ia += ia
344
 
                region_ib += ib
345
 
                reg = self.mismatch_region(next_a, region_ia, next_b,
346
 
                                           region_ib)
347
 
                if reg is not None:
348
 
                    yield reg
349
 
                yield 'same', region_ia, region_len+region_ia
350
 
                next_a = region_ia + region_len
351
 
                next_b = region_ib + region_len
352
 
            reg = self.mismatch_region(next_a, amatch, next_b, bmatch)
353
 
            if reg is not None:
354
 
                yield reg
355
 
 
356
 
    @staticmethod
357
 
    def mismatch_region(next_a, region_ia,  next_b, region_ib):
358
 
        if next_a < region_ia or next_b < region_ib:
359
 
            return 'conflict', None, None, next_a, region_ia, next_b, region_ib
360
 
 
361
 
    def find_sync_regions(self):
362
 
        """Return a list of sync regions, where both descendents match the base.
363
 
 
364
 
        Generates a list of (base1, base2, a1, a2, b1, b2).  There is
365
 
        always a zero-length sync region at the end of all the files.
366
 
        """
367
 
 
368
 
        ia = ib = 0
369
 
        amatches = bzrlib.patiencediff.PatienceSequenceMatcher(
370
 
                None, self.base, self.a).get_matching_blocks()
371
 
        bmatches = bzrlib.patiencediff.PatienceSequenceMatcher(
372
 
                None, self.base, self.b).get_matching_blocks()
373
 
        len_a = len(amatches)
374
 
        len_b = len(bmatches)
375
 
 
376
 
        sl = []
377
 
 
378
 
        while ia < len_a and ib < len_b:
379
 
            abase, amatch, alen = amatches[ia]
380
 
            bbase, bmatch, blen = bmatches[ib]
381
 
 
382
 
            # there is an unconflicted block at i; how long does it
383
 
            # extend?  until whichever one ends earlier.
384
 
            i = intersect((abase, abase+alen), (bbase, bbase+blen))
385
 
            if i:
386
 
                intbase = i[0]
387
 
                intend = i[1]
388
 
                intlen = intend - intbase
389
 
 
390
 
                # found a match of base[i[0], i[1]]; this may be less than
391
 
                # the region that matches in either one
392
 
                assert intlen <= alen
393
 
                assert intlen <= blen
394
 
                assert abase <= intbase
395
 
                assert bbase <= intbase
396
 
 
397
 
                asub = amatch + (intbase - abase)
398
 
                bsub = bmatch + (intbase - bbase)
399
 
                aend = asub + intlen
400
 
                bend = bsub + intlen
401
 
 
402
 
                assert self.base[intbase:intend] == self.a[asub:aend], \
403
 
                       (self.base[intbase:intend], self.a[asub:aend])
404
 
 
405
 
                assert self.base[intbase:intend] == self.b[bsub:bend]
406
 
 
407
 
                sl.append((intbase, intend,
408
 
                           asub, aend,
409
 
                           bsub, bend))
410
 
 
411
 
            # advance whichever one ends first in the base text
412
 
            if (abase + alen) < (bbase + blen):
413
 
                ia += 1
414
 
            else:
415
 
                ib += 1
416
 
            
417
 
        intbase = len(self.base)
418
 
        abase = len(self.a)
419
 
        bbase = len(self.b)
420
 
        sl.append((intbase, intbase, abase, abase, bbase, bbase))
421
 
 
422
 
        return sl
423
 
 
424
 
    def find_unconflicted(self):
425
 
        """Return a list of ranges in base that are not conflicted."""
426
 
        am = bzrlib.patiencediff.PatienceSequenceMatcher(
427
 
                None, self.base, self.a).get_matching_blocks()
428
 
        bm = bzrlib.patiencediff.PatienceSequenceMatcher(
429
 
                None, self.base, self.b).get_matching_blocks()
430
 
 
431
 
        unc = []
432
 
 
433
 
        while am and bm:
434
 
            # there is an unconflicted block at i; how long does it
435
 
            # extend?  until whichever one ends earlier.
436
 
            a1 = am[0][0]
437
 
            a2 = a1 + am[0][2]
438
 
            b1 = bm[0][0]
439
 
            b2 = b1 + bm[0][2]
440
 
            i = intersect((a1, a2), (b1, b2))
441
 
            if i:
442
 
                unc.append(i)
443
 
 
444
 
            if a2 < b2:
445
 
                del am[0]
446
 
            else:
447
 
                del bm[0]
448
 
                
449
 
        return unc
450
 
 
451
 
 
452
 
def main(argv):
453
 
    # as for diff3 and meld the syntax is "MINE BASE OTHER"
454
 
    a = file(argv[1], 'rt').readlines()
455
 
    base = file(argv[2], 'rt').readlines()
456
 
    b = file(argv[3], 'rt').readlines()
457
 
 
458
 
    m3 = Merge3(base, a, b)
459
 
 
460
 
    #for sr in m3.find_sync_regions():
461
 
    #    print sr
462
 
 
463
 
    # sys.stdout.writelines(m3.merge_lines(name_a=argv[1], name_b=argv[3]))
464
 
    sys.stdout.writelines(m3.merge_annotated())
465
 
 
466
 
 
467
 
if __name__ == '__main__':
468
 
    import sys
469
 
    sys.exit(main(sys.argv))