/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: Martin Pool
  • Date: 2005-07-06 00:37:03 UTC
  • Revision ID: mbp@sourcefrog.net-20050706003703-7ae9be78690ee6d9
- more merge tests from john

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2004, 2005 by 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
 
 
23
def intersect(ra, rb):
 
24
    """Given two ranges return the range where they intersect or None.
 
25
 
 
26
    >>> intersect((0, 10), (0, 6))
 
27
    (0, 6)
 
28
    >>> intersect((0, 10), (5, 15))
 
29
    (5, 10)
 
30
    >>> intersect((0, 10), (10, 15))
 
31
    >>> intersect((0, 9), (10, 15))
 
32
    >>> intersect((0, 9), (7, 15))
 
33
    (7, 9)
 
34
    """
 
35
    assert ra[0] <= ra[1]
 
36
    assert rb[0] <= rb[1]
 
37
    
 
38
    sa = max(ra[0], rb[0])
 
39
    sb = min(ra[1], rb[1])
 
40
    if sa < sb:
 
41
        return sa, sb
 
42
    else:
 
43
        return None
 
44
 
 
45
 
 
46
 
 
47
class Merge3(object):
 
48
    """3-way merge of texts.
 
49
 
 
50
    Given BASE, OTHER, THIS, tries to produce a combined text
 
51
    incorporating the changes from both BASE->OTHER and BASE->THIS.
 
52
    All three will typically be sequences of lines."""
 
53
    def __init__(self, base, a, b):
 
54
        self.base = base
 
55
        self.a = a
 
56
        self.b = b
 
57
        from difflib import SequenceMatcher
 
58
        self.a_ops = SequenceMatcher(None, base, a).get_opcodes()
 
59
        self.b_ops = SequenceMatcher(None, base, b).get_opcodes()
 
60
 
 
61
 
 
62
 
 
63
    def merge_lines(self,
 
64
                    name_a=None,
 
65
                    name_b=None,
 
66
                    start_marker='<<<<<<<<',
 
67
                    mid_marker='========',
 
68
                    end_marker='>>>>>>>>',
 
69
                    show_base=False):
 
70
        """Return merge in cvs-like form.
 
71
        """
 
72
        if name_a:
 
73
            start_marker = start_marker + ' ' + name_a
 
74
        if name_b:
 
75
            end_marker = end_marker + ' ' + name_b
 
76
            
 
77
        for t in self.merge_regions():
 
78
            what = t[0]
 
79
            if what == 'unchanged':
 
80
                for i in range(t[1], t[2]):
 
81
                    yield self.base[i]
 
82
            elif what == 'a' or what == 'same':
 
83
                for i in range(t[1], t[2]):
 
84
                    yield self.a[i]
 
85
            elif what == 'b':
 
86
                for i in range(t[1], t[2]):
 
87
                    yield self.b[i]
 
88
            elif what == 'conflict':
 
89
                yield start_marker + '\n'
 
90
                for i in range(t[3], t[4]):
 
91
                    yield self.a[i]
 
92
                yield mid_marker + '\n'
 
93
                for i in range(t[5], t[6]):
 
94
                    yield self.b[i]
 
95
                yield end_marker + '\n'
 
96
            else:
 
97
                raise ValueError(what)
 
98
        
 
99
        
 
100
 
 
101
 
 
102
 
 
103
    def merge_annotated(self):
 
104
        """Return merge with conflicts, showing origin of lines.
 
105
 
 
106
        Most useful for debugging merge.        
 
107
        """
 
108
        for t in self.merge_regions():
 
109
            what = t[0]
 
110
            if what == 'unchanged':
 
111
                for i in range(t[1], t[2]):
 
112
                    yield 'u | ' + self.base[i]
 
113
            elif what == 'a' or what == 'same':
 
114
                for i in range(t[1], t[2]):
 
115
                    yield what[0] + ' | ' + self.a[i]
 
116
            elif what == 'b':
 
117
                for i in range(t[1], t[2]):
 
118
                    yield 'b | ' + self.b[i]
 
119
            elif what == 'conflict':
 
120
                yield '<<<<\n'
 
121
                for i in range(t[3], t[4]):
 
122
                    yield 'A | ' + self.a[i]
 
123
                yield '----\n'
 
124
                for i in range(t[5], t[6]):
 
125
                    yield 'B | ' + self.b[i]
 
126
                yield '>>>>\n'
 
127
            else:
 
128
                raise ValueError(what)
 
129
        
 
130
        
 
131
 
 
132
 
 
133
 
 
134
    def merge_groups(self):
 
135
        """Yield sequence of line groups.  Each one is a tuple:
 
136
 
 
137
        'unchanged', lines
 
138
             Lines unchanged from base
 
139
 
 
140
        'a', lines
 
141
             Lines taken from a
 
142
 
 
143
        'same', lines
 
144
             Lines taken from a (and equal to b)
 
145
 
 
146
        'b', lines
 
147
             Lines taken from b
 
148
 
 
149
        'conflict', base_lines, a_lines, b_lines
 
150
             Lines from base were changed to either a or b and conflict.
 
151
        """
 
152
        for t in self.merge_regions():
 
153
            what = t[0]
 
154
            if what == 'unchanged':
 
155
                yield what, self.base[t[1]:t[2]]
 
156
            elif what == 'a' or what == 'same':
 
157
                yield what, self.a[t[1]:t[2]]
 
158
            elif what == 'b':
 
159
                yield what, self.b[t[1]:t[2]]
 
160
            elif what == 'conflict':
 
161
                yield (what,
 
162
                       self.base[t[1]:t[2]],
 
163
                       self.a[t[3]:t[4]],
 
164
                       self.b[t[5]:t[6]])
 
165
            else:
 
166
                raise ValueError(what)
 
167
 
 
168
 
 
169
    def merge_regions(self):
 
170
        """Return sequences of matching and conflicting regions.
 
171
 
 
172
        This returns tuples, where the first value says what kind we
 
173
        have:
 
174
 
 
175
        'unchanged', start, end
 
176
             Take a region of base[start:end]
 
177
 
 
178
        'same', astart, aend
 
179
             b and a are different from base but give the same result
 
180
 
 
181
        'a', start, end
 
182
             Non-clashing insertion from a[start:end]
 
183
 
 
184
        Method is as follows:
 
185
 
 
186
        The two sequences align only on regions which match the base
 
187
        and both descendents.  These are found by doing a two-way diff
 
188
        of each one against the base, and then finding the
 
189
        intersections between those regions.  These "sync regions"
 
190
        are by definition unchanged in both and easily dealt with.
 
191
 
 
192
        The regions in between can be in any of three cases:
 
193
        conflicted, or changed on only one side.
 
194
        """
 
195
 
 
196
        # section a[0:ia] has been disposed of, etc
 
197
        iz = ia = ib = 0
 
198
        
 
199
        for zmatch, zend, amatch, aend, bmatch, bend in self.find_sync_regions():
 
200
            matchlen = zend - zmatch
 
201
            assert matchlen >= 0
 
202
            assert matchlen == (aend - amatch)
 
203
            assert matchlen == (bend - bmatch)
 
204
            
 
205
            len_a = amatch - ia
 
206
            len_b = bmatch - ib
 
207
            len_base = zmatch - iz
 
208
            assert len_a >= 0
 
209
            assert len_b >= 0
 
210
            assert len_base >= 0
 
211
 
 
212
            if len_a or len_b:
 
213
                lines_base = self.base[iz:zmatch]
 
214
                lines_a = self.a[ia:amatch]
 
215
                lines_b = self.b[ib:bmatch]
 
216
 
 
217
                # we check the len just as a shortcut
 
218
                equal_a = (len_a == len_base
 
219
                           and lines_a == lines_base)
 
220
                equal_b = (len_b == len_base
 
221
                           and lines_b == lines_base)
 
222
                same = (len_a == len_b
 
223
                        and lines_a == lines_b)
 
224
 
 
225
                if same:
 
226
                    yield 'same', ia, amatch
 
227
                elif equal_a and not equal_b:
 
228
                    yield 'b', ib, bmatch
 
229
                elif equal_b and not equal_a:
 
230
                    yield 'a', ia, amatch
 
231
                elif not equal_a and not equal_b:
 
232
                    yield 'conflict', iz, zmatch, ia, amatch, ib, bmatch
 
233
                else:
 
234
                    assert 0
 
235
 
 
236
                ia = amatch
 
237
                ib = bmatch
 
238
            iz = zmatch
 
239
 
 
240
            # if the same part of the base was deleted on both sides
 
241
            # that's OK, we can just skip it.
 
242
 
 
243
                
 
244
            if matchlen > 0:
 
245
                assert ia == amatch
 
246
                assert ib == bmatch
 
247
                assert iz == zmatch
 
248
                
 
249
                yield 'unchanged', zmatch, zend
 
250
                iz = zend
 
251
                ia = aend
 
252
                ib = bend
 
253
        
 
254
 
 
255
        
 
256
    def find_sync_regions(self):
 
257
        """Return a list of sync regions, where both descendents match the base.
 
258
 
 
259
        Generates a list of (base1, base2, a1, a2, b1, b2).  There is
 
260
        always a zero-length sync region at the end of all the files.
 
261
        """
 
262
        from difflib import SequenceMatcher
 
263
        aiter = iter(SequenceMatcher(None, self.base, self.a).get_matching_blocks())
 
264
        biter = iter(SequenceMatcher(None, self.base, self.b).get_matching_blocks())
 
265
 
 
266
        abase, amatch, alen = aiter.next()
 
267
        bbase, bmatch, blen = biter.next()
 
268
 
 
269
        while aiter and biter:
 
270
            # there is an unconflicted block at i; how long does it
 
271
            # extend?  until whichever one ends earlier.
 
272
            i = intersect((abase, abase+alen), (bbase, bbase+blen))
 
273
            if i:
 
274
                intbase = i[0]
 
275
                intend = i[1]
 
276
                intlen = intend - intbase
 
277
 
 
278
                # found a match of base[i[0], i[1]]; this may be less than
 
279
                # the region that matches in either one
 
280
                assert intlen <= alen
 
281
                assert intlen <= blen
 
282
                assert abase <= intbase
 
283
                assert bbase <= intbase
 
284
 
 
285
                asub = amatch + (intbase - abase)
 
286
                bsub = bmatch + (intbase - bbase)
 
287
                aend = asub + intlen
 
288
                bend = bsub + intlen
 
289
 
 
290
                assert self.base[intbase:intend] == self.a[asub:aend], \
 
291
                       (self.base[intbase:intend], self.a[asub:aend])
 
292
                
 
293
                assert self.base[intbase:intend] == self.b[bsub:bend]
 
294
 
 
295
                yield (intbase, intend,
 
296
                       asub, aend,
 
297
                       bsub, bend)
 
298
 
 
299
            # advance whichever one ends first in the base text
 
300
            if (abase + alen) < (bbase + blen):
 
301
                abase, amatch, alen = aiter.next()
 
302
            else:
 
303
                bbase, bmatch, blen = biter.next()
 
304
 
 
305
        intbase = len(self.base)
 
306
        abase = len(self.a)
 
307
        bbase = len(self.b)
 
308
        yield (intbase, intbase, abase, abase, bbase, bbase)
 
309
 
 
310
 
 
311
 
 
312
    def find_unconflicted(self):
 
313
        """Return a list of ranges in base that are not conflicted."""
 
314
        from difflib import SequenceMatcher
 
315
 
 
316
        import re
 
317
 
 
318
        # don't sync-up on lines containing only blanks or pounds
 
319
        junk_re = re.compile(r'^[ \t#]*$')
 
320
        
 
321
        am = SequenceMatcher(junk_re.match, self.base, self.a).get_matching_blocks()
 
322
        bm = SequenceMatcher(junk_re.match, self.base, self.b).get_matching_blocks()
 
323
 
 
324
        unc = []
 
325
 
 
326
        while am and bm:
 
327
            # there is an unconflicted block at i; how long does it
 
328
            # extend?  until whichever one ends earlier.
 
329
            a1 = am[0][0]
 
330
            a2 = a1 + am[0][2]
 
331
            b1 = bm[0][0]
 
332
            b2 = b1 + bm[0][2]
 
333
            i = intersect((a1, a2), (b1, b2))
 
334
            if i:
 
335
                unc.append(i)
 
336
 
 
337
            if a2 < b2:
 
338
                del am[0]
 
339
            else:
 
340
                del bm[0]
 
341
                
 
342
        return unc
 
343
 
 
344
 
 
345
def main(argv):
 
346
    # as for diff3 and meld the syntax is "MINE BASE OTHER"
 
347
    a = file(argv[1], 'rt').readlines()
 
348
    base = file(argv[2], 'rt').readlines()
 
349
    b = file(argv[3], 'rt').readlines()
 
350
 
 
351
    m3 = Merge3(base, a, b)
 
352
 
 
353
    # sys.stdout.writelines(m3.merge_lines(name_a=argv[1], name_b=argv[3]))
 
354
    sys.stdout.writelines(m3.merge_annotated())
 
355
 
 
356
 
 
357
if __name__ == '__main__':
 
358
    import sys
 
359
    sys.exit(main(sys.argv))