~bzr-pqm/bzr/bzr.dev

821 by Martin Pool
- start code for built-in diff3-style resolve
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
823 by Martin Pool
quote
18
# mbp: "you know that thing where cvs gives you conflict markers?"
19
# s: "i hate that."
20
21
821 by Martin Pool
- start code for built-in diff3-style resolve
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
822 by Martin Pool
- Renamed merge3 test suite for easier access.
46
def threeway(baseline, aline, bline):
47
    if baseline == aline:
48
        return bline
49
    elif baseline == bline:
50
        return aline
51
    else:
52
        return [aline, bline]
53
54
55
821 by Martin Pool
- start code for built-in diff3-style resolve
56
class Merge3(object):
57
    """3-way merge of texts.
58
59
    Given BASE, OTHER, THIS, tries to produce a combined text
60
    incorporating the changes from both BASE->OTHER and BASE->THIS.
61
    All three will typically be sequences of lines."""
62
    def __init__(self, base, a, b):
63
        self.base = base
64
        self.a = a
65
        self.b = b
822 by Martin Pool
- Renamed merge3 test suite for easier access.
66
        from difflib import SequenceMatcher
67
        self.a_ops = SequenceMatcher(None, base, a).get_opcodes()
68
        self.b_ops = SequenceMatcher(None, base, b).get_opcodes()
69
70
825 by Martin Pool
- Merge3.find_sync_regions always returns a zero-length sentinal at the end to
71
    def merge_regions(self):
822 by Martin Pool
- Renamed merge3 test suite for easier access.
72
        """Return sequences of matching and conflicting regions.
73
825 by Martin Pool
- Merge3.find_sync_regions always returns a zero-length sentinal at the end to
74
        This returns tuples, where the first value says what kind we
75
        have:
76
77
        'unchanged', start, end
78
             Take a region of base[start:end]
79
80
        'a', start, end
81
             Non-clashing insertion from a[start:end]
82
822 by Martin Pool
- Renamed merge3 test suite for easier access.
83
        Method is as follows:
84
85
        The two sequences align only on regions which match the base
86
        and both descendents.  These are found by doing a two-way diff
87
        of each one against the base, and then finding the
88
        intersections between those regions.  These "sync regions"
89
        are by definition unchanged in both and easily dealt with.
90
91
        The regions in between can be in any of three cases:
92
        conflicted, or changed on only one side.
93
        """
825 by Martin Pool
- Merge3.find_sync_regions always returns a zero-length sentinal at the end to
94
95
        # section a[0:ia] has been disposed of, etc
96
        iz = ia = ib = 0
97
        
98
        for zmatch, zend, amatch, aend, bmatch, bend in self.find_sync_regions():
99
            matchlen = zend - zmatch
100
            assert matchlen >= 0
101
            assert matchlen == (aend - amatch)
102
            assert matchlen == (bend - bmatch)
103
            
104
            if amatch > ia:   # or bmatch > ib:
105
                # got an unmatched region; work out if either
106
                # alternative is the same as the base
107
108
                # kludge: return the whole thing as inserted into A
109
                yield 'a', ia, amatch
110
                ia = amatch
111
112
                
113
            if matchlen > 0:
114
                assert ia == amatch
115
                assert ib == bmatch
116
                assert iz == zmatch
117
                
118
                yield 'unchanged', zmatch, zend
119
                iz = zend
120
                ia = aend
121
                ib = bend
824 by Martin Pool
- Merge3.find_sync_regions yields just a 6-tuple, not a tuple of tuples
122
        
821 by Martin Pool
- start code for built-in diff3-style resolve
123
124
        
822 by Martin Pool
- Renamed merge3 test suite for easier access.
125
    def find_sync_regions(self):
126
        """Return a list of sync regions, where both descendents match the base.
127
825 by Martin Pool
- Merge3.find_sync_regions always returns a zero-length sentinal at the end to
128
        Generates a list of (base1, base2, a1, a2, b1, b2).  There is
129
        always a zero-length sync region at the end of all the files.
821 by Martin Pool
- start code for built-in diff3-style resolve
130
        """
822 by Martin Pool
- Renamed merge3 test suite for easier access.
131
        from difflib import SequenceMatcher
132
        aiter = iter(SequenceMatcher(None, self.base, self.a).get_matching_blocks())
133
        biter = iter(SequenceMatcher(None, self.base, self.b).get_matching_blocks())
134
135
        abase, amatch, alen = aiter.next()
136
        bbase, bmatch, blen = biter.next()
137
138
        while aiter and biter:
139
            # there is an unconflicted block at i; how long does it
140
            # extend?  until whichever one ends earlier.
141
            i = intersect((abase, abase+alen), (bbase, bbase+blen))
142
            if i:
143
                intbase = i[0]
144
                intend = i[1]
145
                intlen = intend - intbase
146
147
                # found a match of base[i[0], i[1]]; this may be less than
148
                # the region that matches in either one
149
                assert intlen <= alen
150
                assert intlen <= blen
151
                assert abase <= intbase
152
                assert bbase <= intbase
153
154
                asub = amatch + (intbase - abase)
155
                bsub = bmatch + (intbase - bbase)
156
                aend = asub + intlen
157
                bend = bsub + intlen
158
159
                assert self.base[intbase:intend] == self.a[asub:aend], \
160
                       (self.base[intbase:intend], self.a[asub:aend])
161
                
162
                assert self.base[intbase:intend] == self.b[bsub:bend]
163
824 by Martin Pool
- Merge3.find_sync_regions yields just a 6-tuple, not a tuple of tuples
164
                yield (intbase, intend,
165
                       asub, aend,
166
                       bsub, bend)
822 by Martin Pool
- Renamed merge3 test suite for easier access.
167
168
            # advance whichever one ends first in the base text
169
            if (abase + alen) < (bbase + blen):
170
                abase, amatch, alen = aiter.next()
171
            else:
172
                bbase, bmatch, blen = biter.next()
173
825 by Martin Pool
- Merge3.find_sync_regions always returns a zero-length sentinal at the end to
174
        intbase = len(self.base)
175
        abase = len(self.a)
176
        bbase = len(self.b)
177
        yield (intbase, intbase, abase, abase, bbase, bbase)
178
821 by Martin Pool
- start code for built-in diff3-style resolve
179
180
181
    def find_unconflicted(self):
182
        """Return a list of ranges in base that are not conflicted."""
183
        from difflib import SequenceMatcher
184
        am = SequenceMatcher(None, self.base, self.a).get_matching_blocks()
185
        bm = SequenceMatcher(None, self.base, self.b).get_matching_blocks()
186
187
        unc = []
188
189
        while am and bm:
190
            # there is an unconflicted block at i; how long does it
191
            # extend?  until whichever one ends earlier.
192
            a1 = am[0][0]
193
            a2 = a1 + am[0][2]
194
            b1 = bm[0][0]
195
            b2 = b1 + bm[0][2]
196
            i = intersect((a1, a2), (b1, b2))
197
            if i:
198
                unc.append(i)
199
200
            if a2 < b2:
201
                del am[0]
202
            else:
203
                del bm[0]
204
                
205
        return unc