1135
1135
for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
1136
1136
assert text_a == text_b
1137
1137
yield "unchanged", text_a
1140
class PlanMerge(object):
1141
"""Plan an annotate merge using on-the-fly annotation"""
1143
def __init__(self, a_rev, b_rev, vf):
1146
:param a_rev: Revision-id of one revision to merge
1147
:param b_rev: Revision-id of the other revision to merge
1148
:param vf: A versionedfile containing both revisions
1152
self.lines_a = vf.get_lines(a_rev)
1153
self.lines_b = vf.get_lines(b_rev)
1155
a_ancestry = set(vf.get_ancestry(a_rev))
1156
b_ancestry = set(vf.get_ancestry(b_rev))
1157
self.uncommon = a_ancestry.symmetric_difference(b_ancestry)
1159
def plan_merge(self):
1160
"""Generate a 'plan' for merging the two revisions.
1162
This involves comparing their texts and determining the cause of
1163
differences. If text A has a line and text B does not, then either the
1164
line was added to text A, or it was deleted from B. Once the causes
1165
are combined, they are written out in the format described in
1166
VersionedFile.plan_merge
1168
blocks = self._get_matching_blocks(self.a_rev, self.b_rev)
1169
new_a = self._find_new(self.a_rev)
1170
new_b = self._find_new(self.b_rev)
1173
a_lines = self.vf.get_lines(self.a_rev)
1174
b_lines = self.vf.get_lines(self.b_rev)
1175
for i, j, n in blocks:
1176
# determine why lines aren't common
1177
for a_index in range(last_i, i):
1178
if a_index in new_a:
1182
yield cause, a_lines[a_index]
1183
for b_index in range(last_j, j):
1184
if b_index in new_b:
1188
yield cause, b_lines[b_index]
1189
# handle common lines
1190
for a_index in range(i, i+n):
1191
yield 'unchanged', a_lines[a_index]
1195
def _get_matching_blocks(self, left_revision, right_revision):
1196
"""Return a description of which sections of two revisions match.
1198
See SequenceMatcher.get_matching_blocks
1200
left_lines = self.vf.get_lines(left_revision)
1201
right_lines = self.vf.get_lines(right_revision)
1202
matcher = patiencediff.PatienceSequenceMatcher(None, left_lines,
1204
return matcher.get_matching_blocks()
1206
def _unique_lines(self, matching_blocks):
1207
"""Analyse matching_blocks to determine which lines are unique
1209
:return: a tuple of (unique_left, unique_right), where the values are
1210
sets of line numbers of unique lines.
1216
for i, j, n in matching_blocks:
1217
unique_left.extend(range(last_i, i))
1218
unique_right.extend(range(last_j, j))
1221
return unique_left, unique_right
1223
def _find_new(self, version_id):
1224
"""Determine which lines are new in the ancestry of this version.
1226
If a lines is present in this version, and not present in any
1227
common ancestor, it is considered new.
1229
if version_id not in self.uncommon:
1231
parents = self.vf.get_parents(version_id)
1232
if len(parents) == 0:
1233
return set(range(len(self.vf.get_lines(version_id))))
1235
for parent in parents:
1236
blocks = self._get_matching_blocks(version_id, parent)
1237
result, unused = self._unique_lines(blocks)
1238
parent_new = self._find_new(parent)
1239
for i, j, n in blocks:
1240
for ii, jj in [(i+r, j+r) for r in range(n)]:
1241
if jj in parent_new:
1246
new.intersection_update(result)