1139
1139
for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
1140
1140
assert text_a == text_b
1141
1141
yield "unchanged", text_a
1144
class _PlanMerge(object):
1145
"""Plan an annotate merge using on-the-fly annotation"""
1147
def __init__(self, a_rev, b_rev, vf):
1150
:param a_rev: Revision-id of one revision to merge
1151
:param b_rev: Revision-id of the other revision to merge
1152
:param vf: A versionedfile containing both revisions
1156
self.lines_a = vf.get_lines(a_rev)
1157
self.lines_b = vf.get_lines(b_rev)
1159
a_ancestry = set(vf.get_ancestry(a_rev, topo_sorted=False))
1160
b_ancestry = set(vf.get_ancestry(b_rev, topo_sorted=False))
1161
self.uncommon = a_ancestry.symmetric_difference(b_ancestry)
1162
self._last_lines = None
1163
self._last_lines_revision_id = None
1165
def plan_merge(self):
1166
"""Generate a 'plan' for merging the two revisions.
1168
This involves comparing their texts and determining the cause of
1169
differences. If text A has a line and text B does not, then either the
1170
line was added to text A, or it was deleted from B. Once the causes
1171
are combined, they are written out in the format described in
1172
VersionedFile.plan_merge
1174
blocks = self._get_matching_blocks(self.a_rev, self.b_rev)
1175
new_a = self._find_new(self.a_rev)
1176
new_b = self._find_new(self.b_rev)
1179
a_lines = self.vf.get_lines(self.a_rev)
1180
b_lines = self.vf.get_lines(self.b_rev)
1181
for i, j, n in blocks:
1182
# determine why lines aren't common
1183
for a_index in range(last_i, i):
1184
if a_index in new_a:
1188
yield cause, a_lines[a_index]
1189
for b_index in range(last_j, j):
1190
if b_index in new_b:
1194
yield cause, b_lines[b_index]
1195
# handle common lines
1196
for a_index in range(i, i+n):
1197
yield 'unchanged', a_lines[a_index]
1201
def _get_matching_blocks(self, left_revision, right_revision):
1202
"""Return a description of which sections of two revisions match.
1204
See SequenceMatcher.get_matching_blocks
1206
if self._last_lines_revision_id == left_revision:
1207
left_lines = self._last_lines
1209
left_lines = self.vf.get_lines(left_revision)
1210
right_lines = self.vf.get_lines(right_revision)
1211
self._last_lines = right_lines
1212
self._last_lines_revision_id = right_revision
1213
matcher = patiencediff.PatienceSequenceMatcher(None, left_lines,
1215
return matcher.get_matching_blocks()
1217
def _unique_lines(self, matching_blocks):
1218
"""Analyse matching_blocks to determine which lines are unique
1220
:return: a tuple of (unique_left, unique_right), where the values are
1221
sets of line numbers of unique lines.
1227
for i, j, n in matching_blocks:
1228
unique_left.extend(range(last_i, i))
1229
unique_right.extend(range(last_j, j))
1232
return unique_left, unique_right
1234
def _find_new(self, version_id):
1235
"""Determine which lines are new in the ancestry of this version.
1237
If a lines is present in this version, and not present in any
1238
common ancestor, it is considered new.
1240
if version_id not in self.uncommon:
1242
parents = self.vf.get_parents(version_id)
1243
if len(parents) == 0:
1244
return set(range(len(self.vf.get_lines(version_id))))
1246
for parent in parents:
1247
blocks = self._get_matching_blocks(version_id, parent)
1248
result, unused = self._unique_lines(blocks)
1249
parent_new = self._find_new(parent)
1250
for i, j, n in blocks:
1251
for ii, jj in [(i+r, j+r) for r in range(n)]:
1252
if jj in parent_new:
1257
new.intersection_update(result)