1135
1146
for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
1136
1147
assert text_a == text_b
1137
1148
yield "unchanged", text_a
1151
class _PlanMerge(object):
1152
"""Plan an annotate merge using on-the-fly annotation"""
1154
def __init__(self, a_rev, b_rev, vf):
1157
:param a_rev: Revision-id of one revision to merge
1158
:param b_rev: Revision-id of the other revision to merge
1159
:param vf: A versionedfile containing both revisions
1163
self.lines_a = vf.get_lines(a_rev)
1164
self.lines_b = vf.get_lines(b_rev)
1166
a_ancestry = set(vf.get_ancestry(a_rev, topo_sorted=False))
1167
b_ancestry = set(vf.get_ancestry(b_rev, topo_sorted=False))
1168
self.uncommon = a_ancestry.symmetric_difference(b_ancestry)
1169
self._last_lines = None
1170
self._last_lines_revision_id = None
1172
def plan_merge(self):
1173
"""Generate a 'plan' for merging the two revisions.
1175
This involves comparing their texts and determining the cause of
1176
differences. If text A has a line and text B does not, then either the
1177
line was added to text A, or it was deleted from B. Once the causes
1178
are combined, they are written out in the format described in
1179
VersionedFile.plan_merge
1181
blocks = self._get_matching_blocks(self.a_rev, self.b_rev)
1182
new_a = self._find_new(self.a_rev)
1183
new_b = self._find_new(self.b_rev)
1186
a_lines = self.vf.get_lines(self.a_rev)
1187
b_lines = self.vf.get_lines(self.b_rev)
1188
for i, j, n in blocks:
1189
# determine why lines aren't common
1190
for a_index in range(last_i, i):
1191
if a_index in new_a:
1195
yield cause, a_lines[a_index]
1196
for b_index in range(last_j, j):
1197
if b_index in new_b:
1201
yield cause, b_lines[b_index]
1202
# handle common lines
1203
for a_index in range(i, i+n):
1204
yield 'unchanged', a_lines[a_index]
1208
def _get_matching_blocks(self, left_revision, right_revision):
1209
"""Return a description of which sections of two revisions match.
1211
See SequenceMatcher.get_matching_blocks
1213
if self._last_lines_revision_id == left_revision:
1214
left_lines = self._last_lines
1216
left_lines = self.vf.get_lines(left_revision)
1217
right_lines = self.vf.get_lines(right_revision)
1218
self._last_lines = right_lines
1219
self._last_lines_revision_id = right_revision
1220
matcher = patiencediff.PatienceSequenceMatcher(None, left_lines,
1222
return matcher.get_matching_blocks()
1224
def _unique_lines(self, matching_blocks):
1225
"""Analyse matching_blocks to determine which lines are unique
1227
:return: a tuple of (unique_left, unique_right), where the values are
1228
sets of line numbers of unique lines.
1234
for i, j, n in matching_blocks:
1235
unique_left.extend(range(last_i, i))
1236
unique_right.extend(range(last_j, j))
1239
return unique_left, unique_right
1241
def _find_new(self, version_id):
1242
"""Determine which lines are new in the ancestry of this version.
1244
If a lines is present in this version, and not present in any
1245
common ancestor, it is considered new.
1247
if version_id not in self.uncommon:
1249
parents = self.vf.get_parents(version_id)
1250
if len(parents) == 0:
1251
return set(range(len(self.vf.get_lines(version_id))))
1253
for parent in parents:
1254
blocks = self._get_matching_blocks(version_id, parent)
1255
result, unused = self._unique_lines(blocks)
1256
parent_new = self._find_new(parent)
1257
for i, j, n in blocks:
1258
for ii, jj in [(i+r, j+r) for r in range(n)]:
1259
if jj in parent_new:
1264
new.intersection_update(result)