1
# Copyright (C) 2006, 2009, 2010 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
# Author: Martin Pool <mbp@canonical.com>
18
# Aaron Bentley <aaron.bentley@utoronto.ca>
21
from bzrlib.lazy_import import lazy_import
22
lazy_import(globals(), """
23
from bzrlib import patiencediff
27
class TextMerge(object):
28
"""Base class for text-mergers
29
Subclasses must implement _merge_struct.
31
Many methods produce or consume structured merge information.
32
This is an iterable of tuples of lists of lines.
33
Each tuple may have a length of 1 - 3, depending on whether the region it
34
represents is conflicted.
36
Unconflicted region tuples have length 1.
37
Conflicted region tuples have length 2 or 3. Index 1 is text_a, e.g. THIS.
38
Index 1 is text_b, e.g. OTHER. Index 2 is optional. If present, it
41
# TODO: Show some version information (e.g. author, date) on conflicted
43
A_MARKER = '<<<<<<< \n'
44
B_MARKER = '>>>>>>> \n'
45
SPLIT_MARKER = '=======\n'
46
def __init__(self, a_marker=A_MARKER, b_marker=B_MARKER,
47
split_marker=SPLIT_MARKER):
48
self.a_marker = a_marker
49
self.b_marker = b_marker
50
self.split_marker = split_marker
52
def _merge_struct(self):
53
"""Return structured merge info. Must be implemented by subclasses.
54
See TextMerge docstring for details on the format.
56
raise NotImplementedError('_merge_struct is abstract')
58
def struct_to_lines(self, struct_iter):
59
"""Convert merge result tuples to lines"""
60
for lines in struct_iter:
68
yield self.split_marker
73
def iter_useful(self, struct_iter):
74
"""Iterate through input tuples, skipping empty ones."""
75
for group in struct_iter:
78
elif len(group) > 1 and len(group[1]) > 0:
81
def merge_lines(self, reprocess=False):
82
"""Produce an iterable of lines, suitable for writing to a file
83
Returns a tuple of (line iterable, conflict indicator)
84
If reprocess is True, a two-way merge will be performed on the
85
intermediate structure, to reduce conflict regions.
89
for group in self.merge_struct(reprocess):
93
return self.struct_to_lines(struct), conflicts
95
def merge_struct(self, reprocess=False):
96
"""Produce structured merge info"""
97
struct_iter = self.iter_useful(self._merge_struct())
99
return self.reprocess_struct(struct_iter)
104
def reprocess_struct(struct_iter):
105
""" Perform a two-way merge on structural merge info.
106
This reduces the size of conflict regions, but breaks the connection
107
between the BASE text and the conflict region.
109
This process may split a single conflict region into several smaller
110
ones, but will not introduce new conflicts.
112
for group in struct_iter:
116
for newgroup in Merge2(group[0], group[1]).merge_struct():
120
class Merge2(TextMerge):
122
In a two way merge, common regions are shown as unconflicting, and uncommon
123
regions produce conflicts.
126
def __init__(self, lines_a, lines_b, a_marker=TextMerge.A_MARKER,
127
b_marker=TextMerge.B_MARKER,
128
split_marker=TextMerge.SPLIT_MARKER):
129
TextMerge.__init__(self, a_marker, b_marker, split_marker)
130
self.lines_a = lines_a
131
self.lines_b = lines_b
133
def _merge_struct(self):
134
"""Return structured merge info.
135
See TextMerge docstring.
137
sm = patiencediff.PatienceSequenceMatcher(
138
None, self.lines_a, self.lines_b)
141
for ai, bi, l in sm.get_matching_blocks():
143
yield(self.lines_a[pos_a:ai], self.lines_b[pos_b:bi])
145
yield(self.lines_a[ai:ai+l],)
148
# final non-matching lines
149
yield(self.lines_a[pos_a:-1], self.lines_b[pos_b:-1])