/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

  • Committer: John Arbash Meinel
  • Date: 2008-03-04 14:25:46 UTC
  • mto: This revision was merged to the branch mainline in revision 3279.
  • Revision ID: john@arbash-meinel.com-20080304142546-zuwwy0o9roo14928
Implement cherrypick support for Merge3
When merging a cherrypick, use a slightly different resolve logic.
When encountering a conflict, the new logic does not include lines that
were present in BASE that are conflicting with OTHER.
This is done since a cherrypick is (by definition) avoiding changes that
are present in the base.
(related to bug #151731)

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
        zzcur = 0
 
291
        bbcur = 0
 
292
        yielded_a = False
 
293
        for region_iz, region_ib, region_len in matches:
 
294
            conflict_z_len = region_iz - zzcur
 
295
            conflict_b_len = region_ib - bbcur
 
296
            if conflict_b_len == 0: # There are no lines in b which conflict,
 
297
                                    # so skip it
 
298
                pass
 
299
            else:
 
300
                if yielded_a:
 
301
                    yield ('conflict', zstart + zzcur, zstart + region_iz,
 
302
                           aend, aend, bstart + bbcur, bstart + region_ib)
 
303
                else:
 
304
                    # The first conflict gets the a-range
 
305
                    yielded_a = True
 
306
                    yield ('conflict', zstart + zzcur, zstart + region_iz,
 
307
                           astart, aend, bstart + bbcur, bstart + region_ib)
 
308
            zzcur = region_iz + region_len
 
309
            bbcur = region_ib + region_len
 
310
        if zzcur != zend - zstart or bbcur != bend - bstart:
 
311
            if yielded_a:
 
312
                yield ('conflict', zstart + zzcur, zstart + region_iz,
 
313
                       aend, aend, bstart + bbcur, bstart + region_ib)
 
314
            else:
 
315
                # The first conflict gets the a-range
 
316
                yielded_a = True
 
317
                yield ('conflict', zstart + zzcur, zstart + region_iz,
 
318
                       astart, aend, bstart + bbcur, bstart + region_ib)
 
319
        if not yielded_a:
 
320
            yield ('conflict', zstart, zend, astart, aend, bstart, bend)
 
321
 
 
322
    def reprocess_merge_regions(self, merge_regions):
 
323
        """Where there are conflict regions, remove the agreed lines.
 
324
 
 
325
        Lines where both A and B have made the same changes are 
 
326
        eliminated.
 
327
        """
 
328
        for region in merge_regions:
 
329
            if region[0] != "conflict":
 
330
                yield region
 
331
                continue
 
332
            type, iz, zmatch, ia, amatch, ib, bmatch = region
 
333
            a_region = self.a[ia:amatch]
 
334
            b_region = self.b[ib:bmatch]
 
335
            matches = bzrlib.patiencediff.PatienceSequenceMatcher(
 
336
                    None, a_region, b_region).get_matching_blocks()
 
337
            next_a = ia
 
338
            next_b = ib
 
339
            for region_ia, region_ib, region_len in matches[:-1]:
 
340
                region_ia += ia
 
341
                region_ib += ib
 
342
                reg = self.mismatch_region(next_a, region_ia, next_b,
 
343
                                           region_ib)
 
344
                if reg is not None:
 
345
                    yield reg
 
346
                yield 'same', region_ia, region_len+region_ia
 
347
                next_a = region_ia + region_len
 
348
                next_b = region_ib + region_len
 
349
            reg = self.mismatch_region(next_a, amatch, next_b, bmatch)
 
350
            if reg is not None:
 
351
                yield reg
 
352
 
 
353
    @staticmethod
 
354
    def mismatch_region(next_a, region_ia,  next_b, region_ib):
 
355
        if next_a < region_ia or next_b < region_ib:
 
356
            return 'conflict', None, None, next_a, region_ia, next_b, region_ib
 
357
 
 
358
    def find_sync_regions(self):
 
359
        """Return a list of sync regions, where both descendents match the base.
 
360
 
 
361
        Generates a list of (base1, base2, a1, a2, b1, b2).  There is
 
362
        always a zero-length sync region at the end of all the files.
 
363
        """
 
364
 
 
365
        ia = ib = 0
 
366
        amatches = bzrlib.patiencediff.PatienceSequenceMatcher(
 
367
                None, self.base, self.a).get_matching_blocks()
 
368
        bmatches = bzrlib.patiencediff.PatienceSequenceMatcher(
 
369
                None, self.base, self.b).get_matching_blocks()
 
370
        len_a = len(amatches)
 
371
        len_b = len(bmatches)
 
372
 
 
373
        sl = []
 
374
 
 
375
        while ia < len_a and ib < len_b:
 
376
            abase, amatch, alen = amatches[ia]
 
377
            bbase, bmatch, blen = bmatches[ib]
 
378
 
 
379
            # there is an unconflicted block at i; how long does it
 
380
            # extend?  until whichever one ends earlier.
 
381
            i = intersect((abase, abase+alen), (bbase, bbase+blen))
 
382
            if i:
 
383
                intbase = i[0]
 
384
                intend = i[1]
 
385
                intlen = intend - intbase
 
386
 
 
387
                # found a match of base[i[0], i[1]]; this may be less than
 
388
                # the region that matches in either one
 
389
                assert intlen <= alen
 
390
                assert intlen <= blen
 
391
                assert abase <= intbase
 
392
                assert bbase <= intbase
 
393
 
 
394
                asub = amatch + (intbase - abase)
 
395
                bsub = bmatch + (intbase - bbase)
 
396
                aend = asub + intlen
 
397
                bend = bsub + intlen
 
398
 
 
399
                # XXX: How much overhead is involved in slicing all of these
 
400
                #      and doing an extra comparison
 
401
                assert self.base[intbase:intend] == self.a[asub:aend], \
 
402
                       (self.base[intbase:intend], self.a[asub:aend])
 
403
 
 
404
                assert self.base[intbase:intend] == self.b[bsub:bend]
 
405
 
 
406
                sl.append((intbase, intend,
 
407
                           asub, aend,
 
408
                           bsub, bend))
 
409
 
 
410
            # advance whichever one ends first in the base text
 
411
            if (abase + alen) < (bbase + blen):
 
412
                ia += 1
 
413
            else:
 
414
                ib += 1
 
415
            
 
416
        intbase = len(self.base)
 
417
        abase = len(self.a)
 
418
        bbase = len(self.b)
 
419
        sl.append((intbase, intbase, abase, abase, bbase, bbase))
 
420
 
 
421
        return sl
 
422
 
 
423
    def find_unconflicted(self):
 
424
        """Return a list of ranges in base that are not conflicted."""
 
425
        am = bzrlib.patiencediff.PatienceSequenceMatcher(
 
426
                None, self.base, self.a).get_matching_blocks()
 
427
        bm = bzrlib.patiencediff.PatienceSequenceMatcher(
 
428
                None, self.base, self.b).get_matching_blocks()
 
429
 
 
430
        unc = []
 
431
 
 
432
        while am and bm:
 
433
            # there is an unconflicted block at i; how long does it
 
434
            # extend?  until whichever one ends earlier.
 
435
            a1 = am[0][0]
 
436
            a2 = a1 + am[0][2]
 
437
            b1 = bm[0][0]
 
438
            b2 = b1 + bm[0][2]
 
439
            i = intersect((a1, a2), (b1, b2))
 
440
            if i:
 
441
                unc.append(i)
 
442
 
 
443
            if a2 < b2:
 
444
                del am[0]
 
445
            else:
 
446
                del bm[0]
 
447
                
 
448
        return unc
 
449
 
 
450
 
 
451
def main(argv):
 
452
    # as for diff3 and meld the syntax is "MINE BASE OTHER"
 
453
    a = file(argv[1], 'rt').readlines()
 
454
    base = file(argv[2], 'rt').readlines()
 
455
    b = file(argv[3], 'rt').readlines()
 
456
 
 
457
    m3 = Merge3(base, a, b)
 
458
 
 
459
    #for sr in m3.find_sync_regions():
 
460
    #    print sr
 
461
 
 
462
    # sys.stdout.writelines(m3.merge_lines(name_a=argv[1], name_b=argv[3]))
 
463
    sys.stdout.writelines(m3.merge_annotated())
 
464
 
 
465
 
 
466
if __name__ == '__main__':
 
467
    import sys
 
468
    sys.exit(main(sys.argv))