/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Aaron Bentley
  • Date: 2007-12-28 20:10:14 UTC
  • mto: (3144.5.2 specific-file)
  • mto: This revision was merged to the branch mainline in revision 3156.
  • Revision ID: abentley@panoramicfeedback.com-20071228201014-hd5g7gsz1ds1mmr4
Implement LCA merge, with problematic conflict markers

Show diffs side-by-side

added added

removed removed

Lines of Context:
1041
1041
            file_group.append(trans_id)
1042
1042
 
1043
1043
 
 
1044
class LCAMerger(WeaveMerger):
 
1045
 
 
1046
    def _merged_lines(self, file_id):
 
1047
        """Generate the merged lines.
 
1048
        There is no distinction between lines that are meant to contain <<<<<<<
 
1049
        and conflicts.
 
1050
        """
 
1051
        if self.cherrypick:
 
1052
            base = self.base_tree
 
1053
        else:
 
1054
            base = None
 
1055
        plan = self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
 
1056
                                                  base=base)
 
1057
        if 'merge' in debug.debug_flags:
 
1058
            plan = list(plan)
 
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)
 
1066
 
 
1067
 
1044
1068
class Diff3Merger(Merge3Merger):
1045
1069
    """Three-way merger using external diff3 for text merging"""
1046
1070
 
1165
1189
            yield "unchanged", text_a
1166
1190
 
1167
1191
 
1168
 
class _PlanMerge(object):
1169
 
    """Plan an annotate merge using on-the-fly annotation"""
 
1192
class _PlanMergeBase(object):
1170
1193
 
1171
1194
    def __init__(self, a_rev, b_rev, vf):
1172
1195
        """Contructor.
1180
1203
        self.lines_a = vf.get_lines(a_rev)
1181
1204
        self.lines_b = vf.get_lines(b_rev)
1182
1205
        self.vf = vf
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
1188
1208
 
1196
1216
        VersionedFile.plan_merge
1197
1217
        """
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)
 
1223
 
 
1224
    def _iter_plan(self, blocks, new_a, killed_b, new_b, killed_a):
1201
1225
        last_i = 0
1202
1226
        last_j = 0
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:
1209
 
                    cause = 'new-a'
1210
 
                else:
1211
 
                    cause = 'killed-b'
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:
1215
 
                    cause = 'new-b'
1216
 
                else:
1217
 
                    cause = 'killed-a'
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]
1222
1241
            last_i = i+n
1223
1242
            last_j = j+n
1224
1243
 
1255
1274
            last_j = j + n
1256
1275
        return unique_left, unique_right
1257
1276
 
 
1277
    @staticmethod
 
1278
    def _subtract_plans(old_plan, new_plan):
 
1279
        matcher = patiencediff.PatienceSequenceMatcher(None, old_plan,
 
1280
                                                       new_plan)
 
1281
        last_j = 0
 
1282
        for i, j, n in matcher.get_matching_blocks():
 
1283
            for jj in range(last_j, j):
 
1284
                yield new_plan[jj]
 
1285
            for jj in range(j, j+n):
 
1286
                plan_line = new_plan[jj]
 
1287
                if plan_line[0] == 'new-b':
 
1288
                    pass
 
1289
                elif plan_line[0] == 'killed-b':
 
1290
                    yield 'unchanged', plan_line[1]
 
1291
                else:
 
1292
                    yield plan_line
 
1293
            last_j = j + n
 
1294
 
 
1295
 
 
1296
class _PlanMerge(_PlanMergeBase):
 
1297
    """Plan an annotate merge using on-the-fly annotation"""
 
1298
 
 
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)
 
1304
 
 
1305
    def _determine_status(self, revision_id, unique_lines):
 
1306
        """Determines the status unique lines versus all lcas.
 
1307
 
 
1308
        Basically, determines why the line is unique to this revision.
 
1309
 
 
1310
        A line may be determined new or killed, but not both.
 
1311
 
 
1312
        :return a tuple of (new_this, killed_other):
 
1313
        """
 
1314
        new = self._find_new(revision_id)
 
1315
        killed = set(unique_lines).difference(new)
 
1316
        return new, killed
 
1317
 
1258
1318
    def _find_new(self, version_id):
1259
1319
        """Determine which lines are new in the ancestry of this version.
1260
1320
 
1281
1341
                new.intersection_update(result)
1282
1342
        return new
1283
1343
 
1284
 
    @staticmethod
1285
 
    def _subtract_plans(old_plan, new_plan):
1286
 
        matcher = patiencediff.PatienceSequenceMatcher(None, old_plan,
1287
 
                                                       new_plan)
1288
 
        last_j = 0
1289
 
        for i, j, n in matcher.get_matching_blocks():
1290
 
            for jj in range(last_j, j):
1291
 
                yield new_plan[jj]
1292
 
            for jj in range(j, j+n):
1293
 
                plan_line = new_plan[jj]
1294
 
                if plan_line[0] == 'new-b':
1295
 
                    pass
1296
 
                elif plan_line[0] == 'killed-b':
1297
 
                    yield 'unchanged', plan_line[1]
1298
 
                else:
1299
 
                    yield plan_line
1300
 
            last_j = j + n
 
1344
 
 
1345
class _PlanLCAMerge(_PlanMergeBase):
 
1346
    """
 
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).
 
1352
 
 
1353
    This is faster, and hopefully produces more useful output.
 
1354
    """
 
1355
 
 
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)
 
1359
 
 
1360
    def _determine_status(self, revision_id, unique_lines):
 
1361
        """Determines the status unique lines versus all lcas.
 
1362
 
 
1363
        Basically, determines why the line is unique to this revision.
 
1364
 
 
1365
        A line may be determined new, killed, or both.
 
1366
 
 
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.
 
1369
 
 
1370
        If a line is determined killed, that means the line was present in
 
1371
        at least one LCA.
 
1372
 
 
1373
        If a line is killed and new, this indicates that the two merge
 
1374
        revisions contain differing conflict resolutions.
 
1375
 
 
1376
        :return a tuple of (new_this, killed_other):
 
1377
        """
 
1378
        new = set()
 
1379
        killed = set()
 
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))
 
1386
        return new, killed