1041
1041
file_group.append(trans_id)
1044
class LCAMerger(WeaveMerger):
1046
def _merged_lines(self, file_id):
1047
"""Generate the merged lines.
1048
There is no distinction between lines that are meant to contain <<<<<<<
1052
base = self.base_tree
1055
plan = self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1057
if 'merge' in debug.debug_flags:
1059
trans_id = self.tt.trans_id_file_id(file_id)
1060
name = self.tt.final_name(trans_id) + '.plan'
1061
contents = ('%10s|%s' % l for l in plan)
1062
self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1063
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1064
'>>>>>>> MERGE-SOURCE\n')
1065
return textmerge.merge_lines(self.reprocess)
1044
1068
class Diff3Merger(Merge3Merger):
1045
1069
"""Three-way merger using external diff3 for text merging"""
1165
1189
yield "unchanged", text_a
1168
class _PlanMerge(object):
1169
"""Plan an annotate merge using on-the-fly annotation"""
1192
class _PlanMergeBase(object):
1171
1194
def __init__(self, a_rev, b_rev, vf):
1180
1203
self.lines_a = vf.get_lines(a_rev)
1181
1204
self.lines_b = vf.get_lines(b_rev)
1183
a_ancestry = set(vf.get_ancestry(a_rev, topo_sorted=False))
1184
b_ancestry = set(vf.get_ancestry(b_rev, topo_sorted=False))
1185
self.uncommon = a_ancestry.symmetric_difference(b_ancestry)
1186
1206
self._last_lines = None
1187
1207
self._last_lines_revision_id = None
1196
1216
VersionedFile.plan_merge
1198
1218
blocks = self._get_matching_blocks(self.a_rev, self.b_rev)
1199
new_a = self._find_new(self.a_rev)
1200
new_b = self._find_new(self.b_rev)
1219
unique_a, unique_b = self._unique_lines(blocks)
1220
new_a, killed_b = self._determine_status(self.a_rev, unique_a)
1221
new_b, killed_a = self._determine_status(self.b_rev, unique_b)
1222
return self._iter_plan(blocks, new_a, killed_b, new_b, killed_a)
1224
def _iter_plan(self, blocks, new_a, killed_b, new_b, killed_a):
1203
a_lines = self.vf.get_lines(self.a_rev)
1204
b_lines = self.vf.get_lines(self.b_rev)
1205
1227
for i, j, n in blocks:
1206
# determine why lines aren't common
1207
1228
for a_index in range(last_i, i):
1208
1229
if a_index in new_a:
1212
yield cause, a_lines[a_index]
1230
yield 'new-a', self.lines_a[a_index]
1231
if a_index in killed_b:
1232
yield 'killed-b', self.lines_a[a_index]
1213
1233
for b_index in range(last_j, j):
1214
1234
if b_index in new_b:
1218
yield cause, b_lines[b_index]
1235
yield 'new-b', self.lines_b[b_index]
1236
if b_index in killed_a:
1237
yield 'killed-a', self.lines_b[b_index]
1219
1238
# handle common lines
1220
1239
for a_index in range(i, i+n):
1221
yield 'unchanged', a_lines[a_index]
1240
yield 'unchanged', self.lines_a[a_index]
1256
1275
return unique_left, unique_right
1278
def _subtract_plans(old_plan, new_plan):
1279
matcher = patiencediff.PatienceSequenceMatcher(None, old_plan,
1282
for i, j, n in matcher.get_matching_blocks():
1283
for jj in range(last_j, j):
1285
for jj in range(j, j+n):
1286
plan_line = new_plan[jj]
1287
if plan_line[0] == 'new-b':
1289
elif plan_line[0] == 'killed-b':
1290
yield 'unchanged', plan_line[1]
1296
class _PlanMerge(_PlanMergeBase):
1297
"""Plan an annotate merge using on-the-fly annotation"""
1299
def __init__(self, a_rev, b_rev, vf):
1300
_PlanMergeBase.__init__(self, a_rev, b_rev, vf)
1301
a_ancestry = set(vf.get_ancestry(a_rev, topo_sorted=False))
1302
b_ancestry = set(vf.get_ancestry(b_rev, topo_sorted=False))
1303
self.uncommon = a_ancestry.symmetric_difference(b_ancestry)
1305
def _determine_status(self, revision_id, unique_lines):
1306
"""Determines the status unique lines versus all lcas.
1308
Basically, determines why the line is unique to this revision.
1310
A line may be determined new or killed, but not both.
1312
:return a tuple of (new_this, killed_other):
1314
new = self._find_new(revision_id)
1315
killed = set(unique_lines).difference(new)
1258
1318
def _find_new(self, version_id):
1259
1319
"""Determine which lines are new in the ancestry of this version.
1281
1341
new.intersection_update(result)
1285
def _subtract_plans(old_plan, new_plan):
1286
matcher = patiencediff.PatienceSequenceMatcher(None, old_plan,
1289
for i, j, n in matcher.get_matching_blocks():
1290
for jj in range(last_j, j):
1292
for jj in range(j, j+n):
1293
plan_line = new_plan[jj]
1294
if plan_line[0] == 'new-b':
1296
elif plan_line[0] == 'killed-b':
1297
yield 'unchanged', plan_line[1]
1345
class _PlanLCAMerge(_PlanMergeBase):
1347
This implementation differs from _PlanMerge.plan_merge in that:
1348
1. comparisons are done against LCAs only
1349
2. cases where a contested line is new versus one LCA but old versus
1350
another are marked as conflicts, by emitting the line as both new-a and
1351
killed-b (or new-b, killed-a).
1353
This is faster, and hopefully produces more useful output.
1356
def __init__(self, a_rev, b_rev, vf, graph):
1357
_PlanMergeBase.__init__(self, a_rev, b_rev, vf)
1358
self.lcas = graph.find_lca(a_rev, b_rev)
1360
def _determine_status(self, revision_id, unique_lines):
1361
"""Determines the status unique lines versus all lcas.
1363
Basically, determines why the line is unique to this revision.
1365
A line may be determined new, killed, or both.
1367
If a line is determined new, that means it was not present at least one
1368
LCA, and is not present in the other merge revision.
1370
If a line is determined killed, that means the line was present in
1373
If a line is killed and new, this indicates that the two merge
1374
revisions contain differing conflict resolutions.
1376
:return a tuple of (new_this, killed_other):
1380
unique_lines = set(unique_lines)
1381
for lca in self.lcas:
1382
blocks = self._get_matching_blocks(revision_id, lca)
1383
unique_vs_lca, _ignored = self._unique_lines(blocks)
1384
new.update(unique_lines.intersection(unique_vs_lca))
1385
killed.update(unique_lines.difference(unique_vs_lca))