~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_merge.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-09-27 04:04:26 UTC
  • mfrom: (3744.1.2 jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20080927040426-dv3yctm2h2fd2bzf
(jam) Merge 1.7.1rc1 back into bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
    conflicts,
22
22
    errors,
23
23
    knit,
 
24
    memorytree,
24
25
    merge as _mod_merge,
25
26
    option,
26
27
    progress,
 
28
    tests,
27
29
    transform,
28
30
    versionedfile,
29
31
    )
1107
1109
    def test_merge_move_and_change(self):
1108
1110
        self.expectFailure("lca merge doesn't conflict for move and change",
1109
1111
            super(TestLCAMerge, self).test_merge_move_and_change)
 
1112
 
 
1113
 
 
1114
class LoggingMerger(object):
 
1115
    # These seem to be the required attributes
 
1116
    requires_base = False
 
1117
    supports_reprocess = False
 
1118
    supports_show_base = False
 
1119
    supports_cherrypick = False
 
1120
    # We intentionally do not define supports_lca_trees
 
1121
 
 
1122
    def __init__(self, *args, **kwargs):
 
1123
        self.args = args
 
1124
        self.kwargs = kwargs
 
1125
 
 
1126
 
 
1127
class TestMergerBase(TestCaseWithMemoryTransport):
 
1128
    """Common functionality for Merger tests that don't write to disk."""
 
1129
 
 
1130
    def get_builder(self):
 
1131
        builder = self.make_branch_builder('path')
 
1132
        builder.start_series()
 
1133
        self.addCleanup(builder.finish_series)
 
1134
        return builder
 
1135
 
 
1136
    def setup_simple_graph(self):
 
1137
        """Create a simple 3-node graph.
 
1138
 
 
1139
        :return: A BranchBuilder
 
1140
        """
 
1141
        #
 
1142
        #  A
 
1143
        #  |\
 
1144
        #  B C
 
1145
        #
 
1146
        builder = self.get_builder()
 
1147
        builder.build_snapshot('A-id', None,
 
1148
            [('add', ('', None, 'directory', None))])
 
1149
        builder.build_snapshot('C-id', ['A-id'], [])
 
1150
        builder.build_snapshot('B-id', ['A-id'], [])
 
1151
        return builder
 
1152
 
 
1153
    def setup_criss_cross_graph(self):
 
1154
        """Create a 5-node graph with a criss-cross.
 
1155
 
 
1156
        :return: A BranchBuilder
 
1157
        """
 
1158
        # A
 
1159
        # |\
 
1160
        # B C
 
1161
        # |X|
 
1162
        # D E
 
1163
        builder = self.setup_simple_graph()
 
1164
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1165
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1166
        return builder
 
1167
 
 
1168
    def make_Merger(self, builder, other_revision_id,
 
1169
                    interesting_files=None, interesting_ids=None):
 
1170
        """Make a Merger object from a branch builder"""
 
1171
        mem_tree = memorytree.MemoryTree.create_on_branch(builder.get_branch())
 
1172
        mem_tree.lock_write()
 
1173
        self.addCleanup(mem_tree.unlock)
 
1174
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
1175
            mem_tree, other_revision_id)
 
1176
        merger.set_interesting_files(interesting_files)
 
1177
        # It seems there is no matching function for set_interesting_ids
 
1178
        merger.interesting_ids = interesting_ids
 
1179
        merger.merge_type = _mod_merge.Merge3Merger
 
1180
        return merger
 
1181
 
 
1182
 
 
1183
class TestMergerInMemory(TestMergerBase):
 
1184
 
 
1185
    def test_find_base(self):
 
1186
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1187
        self.assertEqual('A-id', merger.base_rev_id)
 
1188
        self.assertFalse(merger._is_criss_cross)
 
1189
        self.assertIs(None, merger._lca_trees)
 
1190
 
 
1191
    def test_find_base_criss_cross(self):
 
1192
        builder = self.setup_criss_cross_graph()
 
1193
        merger = self.make_Merger(builder, 'E-id')
 
1194
        self.assertEqual('A-id', merger.base_rev_id)
 
1195
        self.assertTrue(merger._is_criss_cross)
 
1196
        self.assertEqual(['B-id', 'C-id'], [t.get_revision_id()
 
1197
                                            for t in merger._lca_trees])
 
1198
        # If we swap the order, we should get a different lca order
 
1199
        builder.build_snapshot('F-id', ['E-id'], [])
 
1200
        merger = self.make_Merger(builder, 'D-id')
 
1201
        self.assertEqual(['C-id', 'B-id'], [t.get_revision_id()
 
1202
                                            for t in merger._lca_trees])
 
1203
 
 
1204
    def test_find_base_triple_criss_cross(self):
 
1205
        #       A-.
 
1206
        #      / \ \
 
1207
        #     B   C F # F is merged into both branches
 
1208
        #     |\ /| |
 
1209
        #     | X | |\
 
1210
        #     |/ \| | :
 
1211
        #   : D   E |
 
1212
        #    \|   |/
 
1213
        #     G   H
 
1214
        builder = self.setup_criss_cross_graph()
 
1215
        builder.build_snapshot('F-id', ['A-id'], [])
 
1216
        builder.build_snapshot('H-id', ['E-id', 'F-id'], [])
 
1217
        builder.build_snapshot('G-id', ['D-id', 'F-id'], [])
 
1218
        merger = self.make_Merger(builder, 'H-id')
 
1219
        self.assertEqual(['B-id', 'C-id', 'F-id'],
 
1220
                         [t.get_revision_id() for t in merger._lca_trees])
 
1221
 
 
1222
    def test_no_criss_cross_passed_to_merge_type(self):
 
1223
        class LCATreesMerger(LoggingMerger):
 
1224
            supports_lca_trees = True
 
1225
 
 
1226
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1227
        merger.merge_type = LCATreesMerger
 
1228
        merge_obj = merger.make_merger()
 
1229
        self.assertIsInstance(merge_obj, LCATreesMerger)
 
1230
        self.assertFalse('lca_trees' in merge_obj.kwargs)
 
1231
 
 
1232
    def test_criss_cross_passed_to_merge_type(self):
 
1233
        merger = self.make_Merger(self.setup_criss_cross_graph(), 'E-id')
 
1234
        merger.merge_type = _mod_merge.Merge3Merger
 
1235
        merge_obj = merger.make_merger()
 
1236
        self.assertEqual(['B-id', 'C-id'], [t.get_revision_id()
 
1237
                                            for t in merger._lca_trees])
 
1238
 
 
1239
    def test_criss_cross_not_supported_merge_type(self):
 
1240
        merger = self.make_Merger(self.setup_criss_cross_graph(), 'E-id')
 
1241
        # We explicitly do not define supports_lca_trees
 
1242
        merger.merge_type = LoggingMerger
 
1243
        merge_obj = merger.make_merger()
 
1244
        self.assertIsInstance(merge_obj, LoggingMerger)
 
1245
        self.assertFalse('lca_trees' in merge_obj.kwargs)
 
1246
 
 
1247
    def test_criss_cross_unsupported_merge_type(self):
 
1248
        class UnsupportedLCATreesMerger(LoggingMerger):
 
1249
            supports_lca_trees = False
 
1250
 
 
1251
        merger = self.make_Merger(self.setup_criss_cross_graph(), 'E-id')
 
1252
        merger.merge_type = UnsupportedLCATreesMerger
 
1253
        merge_obj = merger.make_merger()
 
1254
        self.assertIsInstance(merge_obj, UnsupportedLCATreesMerger)
 
1255
        self.assertFalse('lca_trees' in merge_obj.kwargs)
 
1256
 
 
1257
 
 
1258
class TestMergerEntriesLCA(TestMergerBase):
 
1259
 
 
1260
    def make_merge_obj(self, builder, other_revision_id,
 
1261
                       interesting_files=None, interesting_ids=None):
 
1262
        merger = self.make_Merger(builder, other_revision_id,
 
1263
            interesting_files=interesting_files,
 
1264
            interesting_ids=interesting_ids)
 
1265
        return merger.make_merger()
 
1266
 
 
1267
    def test_simple(self):
 
1268
        builder = self.get_builder()
 
1269
        builder.build_snapshot('A-id', None,
 
1270
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1271
             ('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1272
        builder.build_snapshot('C-id', ['A-id'],
 
1273
            [('modify', ('a-id', 'a\nb\nC\nc\n'))])
 
1274
        builder.build_snapshot('B-id', ['A-id'],
 
1275
            [('modify', ('a-id', 'a\nB\nb\nc\n'))])
 
1276
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1277
            [('modify', ('a-id', 'a\nB\nb\nC\nc\nE\n'))])
 
1278
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1279
            [('modify', ('a-id', 'a\nB\nb\nC\nc\n'))])
 
1280
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1281
 
 
1282
        self.assertEqual(['B-id', 'C-id'], [t.get_revision_id()
 
1283
                                            for t in merge_obj._lca_trees])
 
1284
        self.assertEqual('A-id', merge_obj.base_tree.get_revision_id())
 
1285
        entries = list(merge_obj._entries_lca())
 
1286
 
 
1287
        # (file_id, changed, parents, names, executable)
 
1288
        # BASE, lca1, lca2, OTHER, THIS
 
1289
        root_id = 'a-root-id'
 
1290
        self.assertEqual([('a-id', True,
 
1291
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1292
                           ((u'a', [u'a', u'a']), u'a', u'a'),
 
1293
                           ((False, [False, False]), False, False)),
 
1294
                         ], entries)
 
1295
 
 
1296
    def test_not_in_base(self):
 
1297
        # LCAs all have the same last-modified revision for the file, as do
 
1298
        # the tips, but the base has something different
 
1299
        #       A    base, doesn't have the file
 
1300
        #       |\
 
1301
        #       B C  B introduces 'foo', C introduces 'bar'
 
1302
        #       |X|
 
1303
        #       D E  D and E now both have 'foo' and 'bar'
 
1304
        #       |X|
 
1305
        #       F G  the files are now in F, G, D and E, but not in A
 
1306
        #            G modifies 'bar'
 
1307
 
 
1308
        builder = self.get_builder()
 
1309
        builder.build_snapshot('A-id', None,
 
1310
            [('add', (u'', 'a-root-id', 'directory', None))])
 
1311
        builder.build_snapshot('B-id', ['A-id'],
 
1312
            [('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
 
1313
        builder.build_snapshot('C-id', ['A-id'],
 
1314
            [('add', (u'bar', 'bar-id', 'file', 'd\ne\nf\n'))])
 
1315
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1316
            [('add', (u'bar', 'bar-id', 'file', 'd\ne\nf\n'))])
 
1317
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1318
            [('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
 
1319
        builder.build_snapshot('G-id', ['E-id', 'D-id'],
 
1320
            [('modify', (u'bar-id', 'd\ne\nf\nG\n'))])
 
1321
        builder.build_snapshot('F-id', ['D-id', 'E-id'], [])
 
1322
        merge_obj = self.make_merge_obj(builder, 'G-id')
 
1323
 
 
1324
        self.assertEqual(['D-id', 'E-id'], [t.get_revision_id()
 
1325
                                            for t in merge_obj._lca_trees])
 
1326
        self.assertEqual('A-id', merge_obj.base_tree.get_revision_id())
 
1327
        entries = list(merge_obj._entries_lca())
 
1328
        root_id = 'a-root-id'
 
1329
        self.assertEqual([('bar-id', True,
 
1330
                           ((None, [root_id, root_id]), root_id, root_id),
 
1331
                           ((None, [u'bar', u'bar']), u'bar', u'bar'),
 
1332
                           ((None, [False, False]), False, False)),
 
1333
                         ], entries)
 
1334
 
 
1335
    def test_not_in_this(self):
 
1336
        builder = self.get_builder()
 
1337
        builder.build_snapshot('A-id', None,
 
1338
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1339
             ('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1340
        builder.build_snapshot('B-id', ['A-id'],
 
1341
            [('modify', ('a-id', 'a\nB\nb\nc\n'))])
 
1342
        builder.build_snapshot('C-id', ['A-id'],
 
1343
            [('modify', ('a-id', 'a\nb\nC\nc\n'))])
 
1344
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1345
            [('modify', ('a-id', 'a\nB\nb\nC\nc\nE\n'))])
 
1346
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1347
            [('unversion', 'a-id')])
 
1348
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1349
 
 
1350
        self.assertEqual(['B-id', 'C-id'], [t.get_revision_id()
 
1351
                                            for t in merge_obj._lca_trees])
 
1352
        self.assertEqual('A-id', merge_obj.base_tree.get_revision_id())
 
1353
 
 
1354
        entries = list(merge_obj._entries_lca())
 
1355
        root_id = 'a-root-id'
 
1356
        self.assertEqual([('a-id', True,
 
1357
                           ((root_id, [root_id, root_id]), root_id, None),
 
1358
                           ((u'a', [u'a', u'a']), u'a', None),
 
1359
                           ((False, [False, False]), False, None)),
 
1360
                         ], entries)
 
1361
 
 
1362
    def test_file_not_in_one_lca(self):
 
1363
        #   A   # just root
 
1364
        #   |\
 
1365
        #   B C # B no file, C introduces a file
 
1366
        #   |X|
 
1367
        #   D E # D and E both have the file, unchanged from C
 
1368
        builder = self.get_builder()
 
1369
        builder.build_snapshot('A-id', None,
 
1370
            [('add', (u'', 'a-root-id', 'directory', None))])
 
1371
        builder.build_snapshot('B-id', ['A-id'], [])
 
1372
        builder.build_snapshot('C-id', ['A-id'],
 
1373
            [('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1374
        builder.build_snapshot('E-id', ['C-id', 'B-id'], []) # Inherited from C
 
1375
        builder.build_snapshot('D-id', ['B-id', 'C-id'], # Merged from C
 
1376
            [('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1377
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1378
 
 
1379
        self.assertEqual(['B-id', 'C-id'], [t.get_revision_id()
 
1380
                                            for t in merge_obj._lca_trees])
 
1381
        self.assertEqual('A-id', merge_obj.base_tree.get_revision_id())
 
1382
 
 
1383
        entries = list(merge_obj._entries_lca())
 
1384
        root_id = 'a-root-id'
 
1385
        self.assertEqual([], entries)
 
1386
 
 
1387
    def test_not_in_other(self):
 
1388
        builder = self.get_builder()
 
1389
        builder.build_snapshot('A-id', None,
 
1390
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1391
             ('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1392
        builder.build_snapshot('B-id', ['A-id'], [])
 
1393
        builder.build_snapshot('C-id', ['A-id'], [])
 
1394
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1395
            [('unversion', 'a-id')])
 
1396
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1397
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1398
 
 
1399
        entries = list(merge_obj._entries_lca())
 
1400
        root_id = 'a-root-id'
 
1401
        self.assertEqual([('a-id', True,
 
1402
                           ((root_id, [root_id, root_id]), None, root_id),
 
1403
                           ((u'a', [u'a', u'a']), None, u'a'),
 
1404
                           ((False, [False, False]), None, False)),
 
1405
                         ], entries)
 
1406
 
 
1407
    def test_not_in_other_or_lca(self):
 
1408
        #       A    base, introduces 'foo'
 
1409
        #       |\
 
1410
        #       B C  B nothing, C deletes foo
 
1411
        #       |X|
 
1412
        #       D E  D restores foo (same as B), E leaves it deleted
 
1413
        # We should emit an entry for this
 
1414
        builder = self.get_builder()
 
1415
        builder.build_snapshot('A-id', None,
 
1416
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1417
             ('add', (u'foo', 'foo-id', 'file', 'content\n'))])
 
1418
        builder.build_snapshot('B-id', ['A-id'], [])
 
1419
        builder.build_snapshot('C-id', ['A-id'],
 
1420
            [('unversion', 'foo-id')])
 
1421
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1422
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1423
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1424
 
 
1425
        entries = list(merge_obj._entries_lca())
 
1426
        root_id = 'a-root-id'
 
1427
        self.assertEqual([('foo-id', True,
 
1428
                           ((root_id, [root_id, None]), None, root_id),
 
1429
                           ((u'foo', [u'foo', None]), None, 'foo'),
 
1430
                           ((False, [False, None]), None, False)),
 
1431
                         ], entries)
 
1432
 
 
1433
    def test_only_in_one_lca(self):
 
1434
        builder = self.get_builder()
 
1435
        builder.build_snapshot('A-id', None,
 
1436
            [('add', (u'', 'a-root-id', 'directory', None))])
 
1437
        builder.build_snapshot('B-id', ['A-id'], [])
 
1438
        builder.build_snapshot('C-id', ['A-id'],
 
1439
            [('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1440
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1441
            [('unversion', 'a-id')])
 
1442
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1443
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1444
 
 
1445
        entries = list(merge_obj._entries_lca())
 
1446
        root_id = 'a-root-id'
 
1447
        self.assertEqual([('a-id', True,
 
1448
                           ((None, [None, root_id]), None, None),
 
1449
                           ((None, [None, u'a']), None, None),
 
1450
                           ((None, [None, False]), None, None)),
 
1451
                         ], entries)
 
1452
 
 
1453
    def test_only_in_other(self):
 
1454
        builder = self.get_builder()
 
1455
        builder.build_snapshot('A-id', None,
 
1456
            [('add', (u'', 'a-root-id', 'directory', None))])
 
1457
        builder.build_snapshot('B-id', ['A-id'], [])
 
1458
        builder.build_snapshot('C-id', ['A-id'], [])
 
1459
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1460
            [('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1461
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1462
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1463
 
 
1464
        entries = list(merge_obj._entries_lca())
 
1465
        root_id = 'a-root-id'
 
1466
        self.assertEqual([('a-id', True,
 
1467
                           ((None, [None, None]), root_id, None),
 
1468
                           ((None, [None, None]), u'a', None),
 
1469
                           ((None, [None, None]), False, None)),
 
1470
                         ], entries)
 
1471
 
 
1472
    def test_one_lca_supersedes(self):
 
1473
        # One LCA supersedes the other LCAs last modified value, but the
 
1474
        # value is not the same as BASE.
 
1475
        #       A    base, introduces 'foo', last mod A
 
1476
        #       |\
 
1477
        #       B C  B modifies 'foo' (mod B), C does nothing (mod A)
 
1478
        #       |X|
 
1479
        #       D E  D does nothing (mod B), E updates 'foo' (mod E)
 
1480
        #       |X|
 
1481
        #       F G  F updates 'foo' (mod F). G does nothing (mod E)
 
1482
        #
 
1483
        #   At this point, G should not be considered to modify 'foo', even
 
1484
        #   though its LCAs disagree. This is because the modification in E
 
1485
        #   completely supersedes the value in D.
 
1486
        builder = self.get_builder()
 
1487
        builder.build_snapshot('A-id', None,
 
1488
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1489
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1490
        builder.build_snapshot('C-id', ['A-id'], [])
 
1491
        builder.build_snapshot('B-id', ['A-id'],
 
1492
            [('modify', ('foo-id', 'B content\n'))])
 
1493
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1494
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1495
            [('modify', ('foo-id', 'E content\n'))])
 
1496
        builder.build_snapshot('G-id', ['E-id', 'D-id'], [])
 
1497
        builder.build_snapshot('F-id', ['D-id', 'E-id'],
 
1498
            [('modify', ('foo-id', 'F content\n'))])
 
1499
        merge_obj = self.make_merge_obj(builder, 'G-id')
 
1500
 
 
1501
        self.assertEqual([], list(merge_obj._entries_lca()))
 
1502
 
 
1503
    def test_one_lca_supersedes_path(self):
 
1504
        # Double-criss-cross merge, the ultimate base value is different from
 
1505
        # the intermediate.
 
1506
        #   A    value 'foo'
 
1507
        #   |\
 
1508
        #   B C  B value 'bar', C = 'foo'
 
1509
        #   |X|
 
1510
        #   D E  D = 'bar', E supersedes to 'bing'
 
1511
        #   |X|
 
1512
        #   F G  F = 'bing', G supersedes to 'barry'
 
1513
        #
 
1514
        # In this case, we technically should not care about the value 'bar' for
 
1515
        # D, because it was clearly superseded by E's 'bing'. The
 
1516
        # per-file/attribute graph would actually look like:
 
1517
        #   A
 
1518
        #   |
 
1519
        #   B
 
1520
        #   |
 
1521
        #   E
 
1522
        #   |
 
1523
        #   G
 
1524
        #
 
1525
        # Because the other side of the merge never modifies the value, it just
 
1526
        # takes the value from the merge.
 
1527
        #
 
1528
        # ATM this fails because we will prune 'foo' from the LCAs, but we
 
1529
        # won't prune 'bar'. This is getting far off into edge-case land, so we
 
1530
        # aren't supporting it yet.
 
1531
        #
 
1532
        builder = self.get_builder()
 
1533
        builder.build_snapshot('A-id', None,
 
1534
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1535
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1536
        builder.build_snapshot('C-id', ['A-id'], [])
 
1537
        builder.build_snapshot('B-id', ['A-id'],
 
1538
            [('rename', ('foo', 'bar'))])
 
1539
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1540
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1541
            [('rename', ('foo', 'bing'))]) # override to bing
 
1542
        builder.build_snapshot('G-id', ['E-id', 'D-id'],
 
1543
            [('rename', ('bing', 'barry'))]) # override to barry
 
1544
        builder.build_snapshot('F-id', ['D-id', 'E-id'],
 
1545
            [('rename', ('bar', 'bing'))]) # Merge in E's change
 
1546
        merge_obj = self.make_merge_obj(builder, 'G-id')
 
1547
 
 
1548
        self.expectFailure("We don't do an actual heads() check on lca values,"
 
1549
            " or use the per-attribute graph",
 
1550
            self.assertEqual, [], list(merge_obj._entries_lca()))
 
1551
 
 
1552
    def test_one_lca_accidentally_pruned(self):
 
1553
        # Another incorrect resolution from the same basic flaw:
 
1554
        #   A    value 'foo'
 
1555
        #   |\
 
1556
        #   B C  B value 'bar', C = 'foo'
 
1557
        #   |X|
 
1558
        #   D E  D = 'bar', E reverts to 'foo'
 
1559
        #   |X|
 
1560
        #   F G  F = 'bing', G switches to 'bar'
 
1561
        #
 
1562
        # 'bar' will not be seen as an interesting change, because 'foo' will
 
1563
        # be pruned from the LCAs, even though it was newly introduced by E
 
1564
        # (superseding B).
 
1565
        builder = self.get_builder()
 
1566
        builder.build_snapshot('A-id', None,
 
1567
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1568
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1569
        builder.build_snapshot('C-id', ['A-id'], [])
 
1570
        builder.build_snapshot('B-id', ['A-id'],
 
1571
            [('rename', ('foo', 'bar'))])
 
1572
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1573
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1574
        builder.build_snapshot('G-id', ['E-id', 'D-id'],
 
1575
            [('rename', ('foo', 'bar'))])
 
1576
        builder.build_snapshot('F-id', ['D-id', 'E-id'],
 
1577
            [('rename', ('bar', 'bing'))]) # should end up conflicting
 
1578
        merge_obj = self.make_merge_obj(builder, 'G-id')
 
1579
 
 
1580
        entries = list(merge_obj._entries_lca())
 
1581
        root_id = 'a-root-id'
 
1582
        self.expectFailure("We prune values from BASE even when relevant.",
 
1583
            self.assertEqual,
 
1584
                [('foo-id', False,
 
1585
                  ((root_id, [root_id, root_id]), root_id, root_id),
 
1586
                  ((u'foo', [u'bar', u'foo']), u'bar', u'bing'),
 
1587
                  ((False, [False, False]), False, False)),
 
1588
                ], entries)
 
1589
 
 
1590
    def test_both_sides_revert(self):
 
1591
        # Both sides of a criss-cross revert the text to the lca
 
1592
        #       A    base, introduces 'foo'
 
1593
        #       |\
 
1594
        #       B C  B modifies 'foo', C modifies 'foo'
 
1595
        #       |X|
 
1596
        #       D E  D reverts to B, E reverts to C
 
1597
        # This should conflict
 
1598
        builder = self.get_builder()
 
1599
        builder.build_snapshot('A-id', None,
 
1600
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1601
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1602
        builder.build_snapshot('B-id', ['A-id'],
 
1603
            [('modify', ('foo-id', 'B content\n'))])
 
1604
        builder.build_snapshot('C-id', ['A-id'],
 
1605
            [('modify', ('foo-id', 'C content\n'))])
 
1606
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1607
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1608
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1609
 
 
1610
        entries = list(merge_obj._entries_lca())
 
1611
        root_id = 'a-root-id'
 
1612
        self.assertEqual([('foo-id', True,
 
1613
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1614
                           ((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
 
1615
                           ((False, [False, False]), False, False)),
 
1616
                         ], entries)
 
1617
 
 
1618
    def test_different_lca_resolve_one_side_updates_content(self):
 
1619
        # Both sides converge, but then one side updates the text.
 
1620
        #       A    base, introduces 'foo'
 
1621
        #       |\
 
1622
        #       B C  B modifies 'foo', C modifies 'foo'
 
1623
        #       |X|
 
1624
        #       D E  D reverts to B, E reverts to C
 
1625
        #       |
 
1626
        #       F    F updates to a new value
 
1627
        # We need to emit an entry for 'foo', because D & E differed on the
 
1628
        # merge resolution
 
1629
        builder = self.get_builder()
 
1630
        builder.build_snapshot('A-id', None,
 
1631
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1632
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1633
        builder.build_snapshot('B-id', ['A-id'],
 
1634
            [('modify', ('foo-id', 'B content\n'))])
 
1635
        builder.build_snapshot('C-id', ['A-id'],
 
1636
            [('modify', ('foo-id', 'C content\n'))])
 
1637
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1638
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1639
        builder.build_snapshot('F-id', ['D-id'],
 
1640
            [('modify', ('foo-id', 'F content\n'))])
 
1641
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1642
 
 
1643
        entries = list(merge_obj._entries_lca())
 
1644
        root_id = 'a-root-id'
 
1645
        self.assertEqual([('foo-id', True,
 
1646
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1647
                           ((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
 
1648
                           ((False, [False, False]), False, False)),
 
1649
                         ], entries)
 
1650
 
 
1651
    def test_same_lca_resolution_one_side_updates_content(self):
 
1652
        # Both sides converge, but then one side updates the text.
 
1653
        #       A    base, introduces 'foo'
 
1654
        #       |\
 
1655
        #       B C  B modifies 'foo', C modifies 'foo'
 
1656
        #       |X|
 
1657
        #       D E  D and E use C's value
 
1658
        #       |
 
1659
        #       F    F updates to a new value
 
1660
        # I think it is a bug that this conflicts, but we don't have a way to
 
1661
        # detect otherwise. And because of:
 
1662
        #   test_different_lca_resolve_one_side_updates_content
 
1663
        # We need to conflict.
 
1664
 
 
1665
        builder = self.get_builder()
 
1666
        builder.build_snapshot('A-id', None,
 
1667
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1668
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1669
        builder.build_snapshot('B-id', ['A-id'],
 
1670
            [('modify', ('foo-id', 'B content\n'))])
 
1671
        builder.build_snapshot('C-id', ['A-id'],
 
1672
            [('modify', ('foo-id', 'C content\n'))])
 
1673
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1674
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1675
            [('modify', ('foo-id', 'C content\n'))]) # Same as E
 
1676
        builder.build_snapshot('F-id', ['D-id'],
 
1677
            [('modify', ('foo-id', 'F content\n'))])
 
1678
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1679
 
 
1680
        entries = list(merge_obj._entries_lca())
 
1681
        root_id = 'a-root-id'
 
1682
        self.expectFailure("We don't detect that LCA resolution was the"
 
1683
                           " same on both sides",
 
1684
            self.assertEqual, [], entries)
 
1685
 
 
1686
    def test_only_path_changed(self):
 
1687
        builder = self.get_builder()
 
1688
        builder.build_snapshot('A-id', None,
 
1689
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1690
             ('add', (u'a', 'a-id', 'file', 'content\n'))])
 
1691
        builder.build_snapshot('B-id', ['A-id'], [])
 
1692
        builder.build_snapshot('C-id', ['A-id'], [])
 
1693
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1694
            [('rename', (u'a', u'b'))])
 
1695
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1696
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1697
        entries = list(merge_obj._entries_lca())
 
1698
        root_id = 'a-root-id'
 
1699
        # The content was not changed, only the path
 
1700
        self.assertEqual([('a-id', False,
 
1701
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1702
                           ((u'a', [u'a', u'a']), u'b', u'a'),
 
1703
                           ((False, [False, False]), False, False)),
 
1704
                         ], entries)
 
1705
 
 
1706
    def test_kind_changed(self):
 
1707
        # Identical content, except 'D' changes a-id into a directory
 
1708
        builder = self.get_builder()
 
1709
        builder.build_snapshot('A-id', None,
 
1710
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1711
             ('add', (u'a', 'a-id', 'file', 'content\n'))])
 
1712
        builder.build_snapshot('B-id', ['A-id'], [])
 
1713
        builder.build_snapshot('C-id', ['A-id'], [])
 
1714
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1715
            [('unversion', 'a-id'),
 
1716
             ('add', (u'a', 'a-id', 'directory', None))])
 
1717
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1718
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1719
        entries = list(merge_obj._entries_lca())
 
1720
        root_id = 'a-root-id'
 
1721
        # Only the kind was changed (content)
 
1722
        self.assertEqual([('a-id', True,
 
1723
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1724
                           ((u'a', [u'a', u'a']), u'a', u'a'),
 
1725
                           ((False, [False, False]), False, False)),
 
1726
                         ], entries)
 
1727
 
 
1728
    def test_this_changed_kind(self):
 
1729
        # Identical content, but THIS changes a file to a directory
 
1730
        builder = self.get_builder()
 
1731
        builder.build_snapshot('A-id', None,
 
1732
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1733
             ('add', (u'a', 'a-id', 'file', 'content\n'))])
 
1734
        builder.build_snapshot('B-id', ['A-id'], [])
 
1735
        builder.build_snapshot('C-id', ['A-id'], [])
 
1736
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1737
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1738
            [('unversion', 'a-id'),
 
1739
             ('add', (u'a', 'a-id', 'directory', None))])
 
1740
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1741
        entries = list(merge_obj._entries_lca())
 
1742
        root_id = 'a-root-id'
 
1743
        # Only the kind was changed (content)
 
1744
        self.assertEqual([], entries)
 
1745
 
 
1746
    def test_interesting_files(self):
 
1747
        # Two files modified, but we should filter one of them
 
1748
        builder = self.get_builder()
 
1749
        builder.build_snapshot('A-id', None,
 
1750
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1751
             ('add', (u'a', 'a-id', 'file', 'content\n')),
 
1752
             ('add', (u'b', 'b-id', 'file', 'content\n'))])
 
1753
        builder.build_snapshot('B-id', ['A-id'], [])
 
1754
        builder.build_snapshot('C-id', ['A-id'], [])
 
1755
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1756
            [('modify', ('a-id', 'new-content\n')),
 
1757
             ('modify', ('b-id', 'new-content\n'))])
 
1758
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1759
        merge_obj = self.make_merge_obj(builder, 'E-id',
 
1760
                                        interesting_files=['b'])
 
1761
        entries = list(merge_obj._entries_lca())
 
1762
        root_id = 'a-root-id'
 
1763
        self.assertEqual([('b-id', True,
 
1764
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1765
                           ((u'b', [u'b', u'b']), u'b', u'b'),
 
1766
                           ((False, [False, False]), False, False)),
 
1767
                         ], entries)
 
1768
 
 
1769
    def test_interesting_file_in_this(self):
 
1770
        # This renamed the file, but it should still match the entry in other
 
1771
        builder = self.get_builder()
 
1772
        builder.build_snapshot('A-id', None,
 
1773
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1774
             ('add', (u'a', 'a-id', 'file', 'content\n')),
 
1775
             ('add', (u'b', 'b-id', 'file', 'content\n'))])
 
1776
        builder.build_snapshot('B-id', ['A-id'], [])
 
1777
        builder.build_snapshot('C-id', ['A-id'], [])
 
1778
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1779
            [('modify', ('a-id', 'new-content\n')),
 
1780
             ('modify', ('b-id', 'new-content\n'))])
 
1781
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1782
            [('rename', ('b', 'c'))])
 
1783
        merge_obj = self.make_merge_obj(builder, 'E-id',
 
1784
                                        interesting_files=['c'])
 
1785
        entries = list(merge_obj._entries_lca())
 
1786
        root_id = 'a-root-id'
 
1787
        self.assertEqual([('b-id', True,
 
1788
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1789
                           ((u'b', [u'b', u'b']), u'b', u'c'),
 
1790
                           ((False, [False, False]), False, False)),
 
1791
                         ], entries)
 
1792
 
 
1793
    def test_interesting_file_in_base(self):
 
1794
        # This renamed the file, but it should still match the entry in BASE
 
1795
        builder = self.get_builder()
 
1796
        builder.build_snapshot('A-id', None,
 
1797
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1798
             ('add', (u'a', 'a-id', 'file', 'content\n')),
 
1799
             ('add', (u'c', 'c-id', 'file', 'content\n'))])
 
1800
        builder.build_snapshot('B-id', ['A-id'],
 
1801
            [('rename', ('c', 'b'))])
 
1802
        builder.build_snapshot('C-id', ['A-id'],
 
1803
            [('rename', ('c', 'b'))])
 
1804
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1805
            [('modify', ('a-id', 'new-content\n')),
 
1806
             ('modify', ('c-id', 'new-content\n'))])
 
1807
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1808
        merge_obj = self.make_merge_obj(builder, 'E-id',
 
1809
                                        interesting_files=['c'])
 
1810
        entries = list(merge_obj._entries_lca())
 
1811
        root_id = 'a-root-id'
 
1812
        self.assertEqual([('c-id', True,
 
1813
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1814
                           ((u'c', [u'b', u'b']), u'b', u'b'),
 
1815
                           ((False, [False, False]), False, False)),
 
1816
                         ], entries)
 
1817
 
 
1818
    def test_interesting_file_in_lca(self):
 
1819
        # This renamed the file, but it should still match the entry in LCA
 
1820
        builder = self.get_builder()
 
1821
        builder.build_snapshot('A-id', None,
 
1822
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1823
             ('add', (u'a', 'a-id', 'file', 'content\n')),
 
1824
             ('add', (u'b', 'b-id', 'file', 'content\n'))])
 
1825
        builder.build_snapshot('B-id', ['A-id'],
 
1826
            [('rename', ('b', 'c'))])
 
1827
        builder.build_snapshot('C-id', ['A-id'], [])
 
1828
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1829
            [('modify', ('a-id', 'new-content\n')),
 
1830
             ('modify', ('b-id', 'new-content\n'))])
 
1831
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1832
            [('rename', ('c', 'b'))])
 
1833
        merge_obj = self.make_merge_obj(builder, 'E-id',
 
1834
                                        interesting_files=['c'])
 
1835
        entries = list(merge_obj._entries_lca())
 
1836
        root_id = 'a-root-id'
 
1837
        self.assertEqual([('b-id', True,
 
1838
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1839
                           ((u'b', [u'c', u'b']), u'b', u'b'),
 
1840
                           ((False, [False, False]), False, False)),
 
1841
                         ], entries)
 
1842
 
 
1843
    def test_interesting_ids(self):
 
1844
        # Two files modified, but we should filter one of them
 
1845
        builder = self.get_builder()
 
1846
        builder.build_snapshot('A-id', None,
 
1847
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1848
             ('add', (u'a', 'a-id', 'file', 'content\n')),
 
1849
             ('add', (u'b', 'b-id', 'file', 'content\n'))])
 
1850
        builder.build_snapshot('B-id', ['A-id'], [])
 
1851
        builder.build_snapshot('C-id', ['A-id'], [])
 
1852
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1853
            [('modify', ('a-id', 'new-content\n')),
 
1854
             ('modify', ('b-id', 'new-content\n'))])
 
1855
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1856
        merge_obj = self.make_merge_obj(builder, 'E-id',
 
1857
                                        interesting_ids=['b-id'])
 
1858
        entries = list(merge_obj._entries_lca())
 
1859
        root_id = 'a-root-id'
 
1860
        self.assertEqual([('b-id', True,
 
1861
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1862
                           ((u'b', [u'b', u'b']), u'b', u'b'),
 
1863
                           ((False, [False, False]), False, False)),
 
1864
                         ], entries)
 
1865
 
 
1866
 
 
1867
 
 
1868
class TestMergerEntriesLCAOnDisk(tests.TestCaseWithTransport):
 
1869
 
 
1870
    def get_builder(self):
 
1871
        builder = self.make_branch_builder('path')
 
1872
        builder.start_series()
 
1873
        self.addCleanup(builder.finish_series)
 
1874
        return builder
 
1875
 
 
1876
    def get_wt_from_builder(self, builder):
 
1877
        """Get a real WorkingTree from the builder."""
 
1878
        the_branch = builder.get_branch()
 
1879
        wt = the_branch.bzrdir.create_workingtree()
 
1880
        # Note: This is a little bit ugly, but we are holding the branch
 
1881
        #       write-locked as part of the build process, and we would like to
 
1882
        #       maintain that. So we just force the WT to re-use the same
 
1883
        #       branch object.
 
1884
        wt._branch = the_branch
 
1885
        wt.lock_write()
 
1886
        self.addCleanup(wt.unlock)
 
1887
        return wt
 
1888
 
 
1889
    def do_merge(self, builder, other_revision_id):
 
1890
        wt = self.get_wt_from_builder(builder)
 
1891
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
1892
            wt, other_revision_id)
 
1893
        merger.merge_type = _mod_merge.Merge3Merger
 
1894
        return wt, merger.do_merge()
 
1895
 
 
1896
    def test_simple_lca(self):
 
1897
        builder = self.get_builder()
 
1898
        builder.build_snapshot('A-id', None,
 
1899
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1900
             ('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
 
1901
        builder.build_snapshot('C-id', ['A-id'], [])
 
1902
        builder.build_snapshot('B-id', ['A-id'], [])
 
1903
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1904
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1905
            [('modify', ('a-id', 'a\nb\nc\nd\ne\nf\n'))])
 
1906
        wt, conflicts = self.do_merge(builder, 'E-id')
 
1907
        self.assertEqual(0, conflicts)
 
1908
        # The merge should have simply update the contents of 'a'
 
1909
        self.assertEqual('a\nb\nc\nd\ne\nf\n', wt.get_file_text('a-id'))
 
1910
 
 
1911
    def test_conflict_without_lca(self):
 
1912
        # This test would cause a merge conflict, unless we use the lca trees
 
1913
        # to determine the real ancestry
 
1914
        #   A       Path at 'foo'
 
1915
        #  / \
 
1916
        # B   C     Path renamed to 'bar' in B
 
1917
        # |\ /|
 
1918
        # | X |
 
1919
        # |/ \|
 
1920
        # D   E     Path at 'bar' in D and E
 
1921
        #     |
 
1922
        #     F     Path at 'baz' in F, which supersedes 'bar' and 'foo'
 
1923
        builder = self.get_builder()
 
1924
        builder.build_snapshot('A-id', None,
 
1925
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1926
             ('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
 
1927
        builder.build_snapshot('C-id', ['A-id'], [])
 
1928
        builder.build_snapshot('B-id', ['A-id'],
 
1929
            [('rename', ('foo', 'bar'))])
 
1930
        builder.build_snapshot('E-id', ['C-id', 'B-id'], # merge the rename
 
1931
            [('rename', ('foo', 'bar'))])
 
1932
        builder.build_snapshot('F-id', ['E-id'],
 
1933
            [('rename', ('bar', 'baz'))])
 
1934
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1935
        wt, conflicts = self.do_merge(builder, 'F-id')
 
1936
        self.assertEqual(0, conflicts)
 
1937
        # The merge should simply recognize that the final rename takes
 
1938
        # precedence
 
1939
        self.assertEqual('baz', wt.id2path('foo-id'))
 
1940
 
 
1941
    def test_other_deletes_lca_renames(self):
 
1942
        # This test would cause a merge conflict, unless we use the lca trees
 
1943
        # to determine the real ancestry
 
1944
        #   A       Path at 'foo'
 
1945
        #  / \
 
1946
        # B   C     Path renamed to 'bar' in B
 
1947
        # |\ /|
 
1948
        # | X |
 
1949
        # |/ \|
 
1950
        # D   E     Path at 'bar' in D and E
 
1951
        #     |
 
1952
        #     F     F deletes 'bar'
 
1953
        builder = self.get_builder()
 
1954
        builder.build_snapshot('A-id', None,
 
1955
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1956
             ('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
 
1957
        builder.build_snapshot('C-id', ['A-id'], [])
 
1958
        builder.build_snapshot('B-id', ['A-id'],
 
1959
            [('rename', ('foo', 'bar'))])
 
1960
        builder.build_snapshot('E-id', ['C-id', 'B-id'], # merge the rename
 
1961
            [('rename', ('foo', 'bar'))])
 
1962
        builder.build_snapshot('F-id', ['E-id'],
 
1963
            [('unversion', 'foo-id')])
 
1964
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1965
        wt, conflicts = self.do_merge(builder, 'F-id')
 
1966
        self.assertEqual(0, conflicts)
 
1967
        self.assertRaises(errors.NoSuchId, wt.id2path, 'foo-id')
 
1968
 
 
1969
    def test_executable_changes(self):
 
1970
        #   A       Path at 'foo'
 
1971
        #  / \
 
1972
        # B   C
 
1973
        # |\ /|
 
1974
        # | X |
 
1975
        # |/ \|
 
1976
        # D   E
 
1977
        #     |
 
1978
        #     F     Executable bit changed
 
1979
        builder = self.get_builder()
 
1980
        builder.build_snapshot('A-id', None,
 
1981
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1982
             ('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
 
1983
        builder.build_snapshot('C-id', ['A-id'], [])
 
1984
        builder.build_snapshot('B-id', ['A-id'], [])
 
1985
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1986
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1987
        # Have to use a real WT, because BranchBuilder doesn't support exec bit
 
1988
        wt = self.get_wt_from_builder(builder)
 
1989
        tt = transform.TreeTransform(wt)
 
1990
        try:
 
1991
            tt.set_executability(True, tt.trans_id_tree_file_id('foo-id'))
 
1992
            tt.apply()
 
1993
        except:
 
1994
            tt.finalize()
 
1995
            raise
 
1996
        self.assertTrue(wt.is_executable('foo-id'))
 
1997
        wt.commit('F-id', rev_id='F-id')
 
1998
        # Reset to D, so that we can merge F
 
1999
        wt.set_parent_ids(['D-id'])
 
2000
        wt.branch.set_last_revision_info(3, 'D-id')
 
2001
        wt.revert()
 
2002
        self.assertFalse(wt.is_executable('foo-id'))
 
2003
        conflicts = wt.merge_from_branch(wt.branch, to_revision='F-id')
 
2004
        self.assertEqual(0, conflicts)
 
2005
        self.assertTrue(wt.is_executable('foo-id'))
 
2006
 
 
2007
    def test_create_symlink(self):
 
2008
        self.requireFeature(tests.SymlinkFeature)
 
2009
        #   A
 
2010
        #  / \
 
2011
        # B   C
 
2012
        # |\ /|
 
2013
        # | X |
 
2014
        # |/ \|
 
2015
        # D   E
 
2016
        #     |
 
2017
        #     F     Add a symlink 'foo' => 'bar'
 
2018
        # Have to use a real WT, because BranchBuilder and MemoryTree don't
 
2019
        # have symlink support
 
2020
        builder = self.get_builder()
 
2021
        builder.build_snapshot('A-id', None,
 
2022
            [('add', (u'', 'a-root-id', 'directory', None))])
 
2023
        builder.build_snapshot('C-id', ['A-id'], [])
 
2024
        builder.build_snapshot('B-id', ['A-id'], [])
 
2025
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2026
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
2027
        # Have to use a real WT, because BranchBuilder doesn't support exec bit
 
2028
        wt = self.get_wt_from_builder(builder)
 
2029
        os.symlink('bar', 'path/foo')
 
2030
        wt.add(['foo'], ['foo-id'])
 
2031
        self.assertEqual('bar', wt.get_symlink_target('foo-id'))
 
2032
        wt.commit('add symlink', rev_id='F-id')
 
2033
        # Reset to D, so that we can merge F
 
2034
        wt.set_parent_ids(['D-id'])
 
2035
        wt.branch.set_last_revision_info(3, 'D-id')
 
2036
        wt.revert()
 
2037
        self.assertIs(None, wt.path2id('foo'))
 
2038
        conflicts = wt.merge_from_branch(wt.branch, to_revision='F-id')
 
2039
        self.assertEqual(0, conflicts)
 
2040
        self.assertEqual('foo-id', wt.path2id('foo'))
 
2041
        self.assertEqual('bar', wt.get_symlink_target('foo-id'))
 
2042
 
 
2043
    def test_both_sides_revert(self):
 
2044
        # Both sides of a criss-cross revert the text to the lca
 
2045
        #       A    base, introduces 'foo'
 
2046
        #       |\
 
2047
        #       B C  B modifies 'foo', C modifies 'foo'
 
2048
        #       |X|
 
2049
        #       D E  D reverts to B, E reverts to C
 
2050
        # This should conflict
 
2051
        # This must be done with a real WorkingTree, because normally their
 
2052
        # inventory contains "None" rather than a real sha1
 
2053
        builder = self.get_builder()
 
2054
        builder.build_snapshot('A-id', None,
 
2055
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2056
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
2057
        builder.build_snapshot('B-id', ['A-id'],
 
2058
            [('modify', ('foo-id', 'B content\n'))])
 
2059
        builder.build_snapshot('C-id', ['A-id'],
 
2060
            [('modify', ('foo-id', 'C content\n'))])
 
2061
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
2062
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2063
        wt, conflicts = self.do_merge(builder, 'E-id')
 
2064
        self.assertEqual(1, conflicts)
 
2065
        self.assertEqualDiff('<<<<<<< TREE\n'
 
2066
                             'B content\n'
 
2067
                             '=======\n'
 
2068
                             'C content\n'
 
2069
                             '>>>>>>> MERGE-SOURCE\n',
 
2070
                             wt.get_file_text('foo-id'))
 
2071
 
 
2072
    def test_modified_symlink(self):
 
2073
        self.requireFeature(tests.SymlinkFeature)
 
2074
        #   A       Create symlink foo => bar
 
2075
        #  / \
 
2076
        # B   C     B relinks foo => baz
 
2077
        # |\ /|
 
2078
        # | X |
 
2079
        # |/ \|
 
2080
        # D   E     D & E have foo => baz
 
2081
        #     |
 
2082
        #     F     F changes it to bing
 
2083
        #
 
2084
        # Merging D & F should result in F cleanly overriding D, because D's
 
2085
        # value actually comes from B
 
2086
 
 
2087
        # Have to use a real WT, because BranchBuilder and MemoryTree don't
 
2088
        # have symlink support
 
2089
        wt = self.make_branch_and_tree('path')
 
2090
        wt.lock_write()
 
2091
        self.addCleanup(wt.unlock)
 
2092
        os.symlink('bar', 'path/foo')
 
2093
        wt.add(['foo'], ['foo-id'])
 
2094
        wt.commit('add symlink', rev_id='A-id')
 
2095
        os.remove('path/foo')
 
2096
        os.symlink('baz', 'path/foo')
 
2097
        wt.commit('foo => baz', rev_id='B-id')
 
2098
        wt.set_last_revision('A-id')
 
2099
        wt.branch.set_last_revision_info(1, 'A-id')
 
2100
        wt.revert()
 
2101
        wt.commit('C', rev_id='C-id')
 
2102
        wt.merge_from_branch(wt.branch, 'B-id')
 
2103
        self.assertEqual('baz', wt.get_symlink_target('foo-id'))
 
2104
        wt.commit('E merges C & B', rev_id='E-id')
 
2105
        os.remove('path/foo')
 
2106
        os.symlink('bing', 'path/foo')
 
2107
        wt.commit('F foo => bing', rev_id='F-id')
 
2108
        wt.set_last_revision('B-id')
 
2109
        wt.branch.set_last_revision_info(2, 'B-id')
 
2110
        wt.revert()
 
2111
        wt.merge_from_branch(wt.branch, 'C-id')
 
2112
        wt.commit('D merges B & C', rev_id='D-id')
 
2113
        conflicts = wt.merge_from_branch(wt.branch, to_revision='F-id')
 
2114
        self.expectFailure("Merge3Merger doesn't use lcas for symlink content",
 
2115
            self.assertEqual, 0, conflicts)
 
2116
        self.assertEqual('bing', wt.get_symlink_target('foo-id'))
 
2117
 
 
2118
    def test_renamed_symlink(self):
 
2119
        self.requireFeature(tests.SymlinkFeature)
 
2120
        #   A       Create symlink foo => bar
 
2121
        #  / \
 
2122
        # B   C     B renames foo => barry
 
2123
        # |\ /|
 
2124
        # | X |
 
2125
        # |/ \|
 
2126
        # D   E     D & E have barry
 
2127
        #     |
 
2128
        #     F     F renames barry to blah
 
2129
        #
 
2130
        # Merging D & F should result in F cleanly overriding D, because D's
 
2131
        # value actually comes from B
 
2132
 
 
2133
        wt = self.make_branch_and_tree('path')
 
2134
        wt.lock_write()
 
2135
        self.addCleanup(wt.unlock)
 
2136
        os.symlink('bar', 'path/foo')
 
2137
        wt.add(['foo'], ['foo-id'])
 
2138
        wt.commit('A add symlink', rev_id='A-id')
 
2139
        wt.rename_one('foo', 'barry')
 
2140
        wt.commit('B foo => barry', rev_id='B-id')
 
2141
        wt.set_last_revision('A-id')
 
2142
        wt.branch.set_last_revision_info(1, 'A-id')
 
2143
        wt.revert()
 
2144
        wt.commit('C', rev_id='C-id')
 
2145
        wt.merge_from_branch(wt.branch, 'B-id')
 
2146
        self.assertEqual('barry', wt.id2path('foo-id'))
 
2147
        self.assertEqual('bar', wt.get_symlink_target('foo-id'))
 
2148
        wt.commit('E merges C & B', rev_id='E-id')
 
2149
        wt.rename_one('barry', 'blah')
 
2150
        wt.commit('F barry => blah', rev_id='F-id')
 
2151
        wt.set_last_revision('B-id')
 
2152
        wt.branch.set_last_revision_info(2, 'B-id')
 
2153
        wt.revert()
 
2154
        wt.merge_from_branch(wt.branch, 'C-id')
 
2155
        wt.commit('D merges B & C', rev_id='D-id')
 
2156
        self.assertEqual('barry', wt.id2path('foo-id'))
 
2157
        # Check the output of the Merger object directly
 
2158
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2159
            wt, 'F-id')
 
2160
        merger.merge_type = _mod_merge.Merge3Merger
 
2161
        merge_obj = merger.make_merger()
 
2162
        root_id = wt.path2id('')
 
2163
        entries = list(merge_obj._entries_lca())
 
2164
        # No content change, just a path change
 
2165
        self.assertEqual([('foo-id', False,
 
2166
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
2167
                           ((u'foo', [u'barry', u'foo']), u'blah', u'barry'),
 
2168
                           ((False, [False, False]), False, False)),
 
2169
                         ], entries)
 
2170
        conflicts = wt.merge_from_branch(wt.branch, to_revision='F-id')
 
2171
        self.assertEqual(0, conflicts)
 
2172
        self.assertEqual('blah', wt.id2path('foo-id'))
 
2173
 
 
2174
    def test_symlink_no_content_change(self):
 
2175
        self.requireFeature(tests.SymlinkFeature)
 
2176
        #   A       Create symlink foo => bar
 
2177
        #  / \
 
2178
        # B   C     B relinks foo => baz
 
2179
        # |\ /|
 
2180
        # | X |
 
2181
        # |/ \|
 
2182
        # D   E     D & E have foo => baz
 
2183
        # |
 
2184
        # F         F has foo => bing
 
2185
        #
 
2186
        # Merging E into F should not cause a conflict, because E doesn't have
 
2187
        # a content change relative to the LCAs (it does relative to A)
 
2188
        wt = self.make_branch_and_tree('path')
 
2189
        wt.lock_write()
 
2190
        self.addCleanup(wt.unlock)
 
2191
        os.symlink('bar', 'path/foo')
 
2192
        wt.add(['foo'], ['foo-id'])
 
2193
        wt.commit('add symlink', rev_id='A-id')
 
2194
        os.remove('path/foo')
 
2195
        os.symlink('baz', 'path/foo')
 
2196
        wt.commit('foo => baz', rev_id='B-id')
 
2197
        wt.set_last_revision('A-id')
 
2198
        wt.branch.set_last_revision_info(1, 'A-id')
 
2199
        wt.revert()
 
2200
        wt.commit('C', rev_id='C-id')
 
2201
        wt.merge_from_branch(wt.branch, 'B-id')
 
2202
        self.assertEqual('baz', wt.get_symlink_target('foo-id'))
 
2203
        wt.commit('E merges C & B', rev_id='E-id')
 
2204
        wt.set_last_revision('B-id')
 
2205
        wt.branch.set_last_revision_info(2, 'B-id')
 
2206
        wt.revert()
 
2207
        wt.merge_from_branch(wt.branch, 'C-id')
 
2208
        wt.commit('D merges B & C', rev_id='D-id')
 
2209
        os.remove('path/foo')
 
2210
        os.symlink('bing', 'path/foo')
 
2211
        wt.commit('F foo => bing', rev_id='F-id')
 
2212
 
 
2213
        # Check the output of the Merger object directly
 
2214
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2215
            wt, 'E-id')
 
2216
        merger.merge_type = _mod_merge.Merge3Merger
 
2217
        merge_obj = merger.make_merger()
 
2218
        # Nothing interesting happened in OTHER relative to BASE
 
2219
        self.assertEqual([], list(merge_obj._entries_lca()))
 
2220
        # Now do a real merge, just to test the rest of the stack
 
2221
        conflicts = wt.merge_from_branch(wt.branch, to_revision='E-id')
 
2222
        self.assertEqual(0, conflicts)
 
2223
        self.assertEqual('bing', wt.get_symlink_target('foo-id'))
 
2224
 
 
2225
    def test_symlink_this_changed_kind(self):
 
2226
        self.requireFeature(tests.SymlinkFeature)
 
2227
        #   A       Nothing
 
2228
        #  / \
 
2229
        # B   C     B creates symlink foo => bar
 
2230
        # |\ /|
 
2231
        # | X |
 
2232
        # |/ \|
 
2233
        # D   E     D changes foo into a file, E has foo => bing
 
2234
        #
 
2235
        # Mostly, this is trying to test that we don't try to os.readlink() on
 
2236
        # a file, or when there is nothing there
 
2237
        wt = self.make_branch_and_tree('path')
 
2238
        wt.lock_write()
 
2239
        self.addCleanup(wt.unlock)
 
2240
        wt.commit('base', rev_id='A-id')
 
2241
        os.symlink('bar', 'path/foo')
 
2242
        wt.add(['foo'], ['foo-id'])
 
2243
        wt.commit('add symlink foo => bar', rev_id='B-id')
 
2244
        wt.set_last_revision('A-id')
 
2245
        wt.branch.set_last_revision_info(1, 'A-id')
 
2246
        wt.revert()
 
2247
        wt.commit('C', rev_id='C-id')
 
2248
        wt.merge_from_branch(wt.branch, 'B-id')
 
2249
        self.assertEqual('bar', wt.get_symlink_target('foo-id'))
 
2250
        os.remove('path/foo')
 
2251
        # We have to change the link in E, or it won't try to do a comparison
 
2252
        os.symlink('bing', 'path/foo')
 
2253
        wt.commit('E merges C & B, overrides to bing', rev_id='E-id')
 
2254
        wt.set_last_revision('B-id')
 
2255
        wt.branch.set_last_revision_info(2, 'B-id')
 
2256
        wt.revert()
 
2257
        wt.merge_from_branch(wt.branch, 'C-id')
 
2258
        os.remove('path/foo')
 
2259
        self.build_tree_contents([('path/foo', 'file content\n')])
 
2260
        # XXX: workaround, WT doesn't detect kind changes unless you do
 
2261
        # iter_changes()
 
2262
        list(wt.iter_changes(wt.basis_tree()))
 
2263
        wt.commit('D merges B & C, makes it a file', rev_id='D-id')
 
2264
 
 
2265
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2266
            wt, 'E-id')
 
2267
        merger.merge_type = _mod_merge.Merge3Merger
 
2268
        merge_obj = merger.make_merger()
 
2269
        entries = list(merge_obj._entries_lca())
 
2270
        root_id = wt.path2id('')
 
2271
        self.assertEqual([('foo-id', True,
 
2272
                           ((None, [root_id, None]), root_id, root_id),
 
2273
                           ((None, [u'foo', None]), u'foo', u'foo'),
 
2274
                           ((None, [False, None]), False, False)),
 
2275
                         ], entries)
 
2276
 
 
2277
    def test_symlink_all_wt(self):
 
2278
        """Check behavior if all trees are Working Trees."""
 
2279
        self.requireFeature(tests.SymlinkFeature)
 
2280
        # The big issue is that entry.symlink_target is None for WorkingTrees.
 
2281
        # So we need to make sure we handle that case correctly.
 
2282
        #   A   foo => bar
 
2283
        #   |\
 
2284
        #   B C B relinks foo => baz
 
2285
        #   |X|
 
2286
        #   D E D & E have foo => baz
 
2287
        #     |
 
2288
        #     F F changes it to bing
 
2289
        # Merging D & F should result in F cleanly overriding D, because D's
 
2290
        # value actually comes from B
 
2291
 
 
2292
        wt = self.make_branch_and_tree('path')
 
2293
        wt.lock_write()
 
2294
        self.addCleanup(wt.unlock)
 
2295
        os.symlink('bar', 'path/foo')
 
2296
        wt.add(['foo'], ['foo-id'])
 
2297
        wt.commit('add symlink', rev_id='A-id')
 
2298
        os.remove('path/foo')
 
2299
        os.symlink('baz', 'path/foo')
 
2300
        wt.commit('foo => baz', rev_id='B-id')
 
2301
        wt.set_last_revision('A-id')
 
2302
        wt.branch.set_last_revision_info(1, 'A-id')
 
2303
        wt.revert()
 
2304
        wt.commit('C', rev_id='C-id')
 
2305
        wt.merge_from_branch(wt.branch, 'B-id')
 
2306
        self.assertEqual('baz', wt.get_symlink_target('foo-id'))
 
2307
        wt.commit('E merges C & B', rev_id='E-id')
 
2308
        os.remove('path/foo')
 
2309
        os.symlink('bing', 'path/foo')
 
2310
        wt.commit('F foo => bing', rev_id='F-id')
 
2311
        wt.set_last_revision('B-id')
 
2312
        wt.branch.set_last_revision_info(2, 'B-id')
 
2313
        wt.revert()
 
2314
        wt.merge_from_branch(wt.branch, 'C-id')
 
2315
        wt.commit('D merges B & C', rev_id='D-id')
 
2316
        wt_base = wt.bzrdir.sprout('base', 'A-id').open_workingtree()
 
2317
        wt_base.lock_read()
 
2318
        self.addCleanup(wt_base.unlock)
 
2319
        wt_lca1 = wt.bzrdir.sprout('b-tree', 'B-id').open_workingtree()
 
2320
        wt_lca1.lock_read()
 
2321
        self.addCleanup(wt_lca1.unlock)
 
2322
        wt_lca2 = wt.bzrdir.sprout('c-tree', 'C-id').open_workingtree()
 
2323
        wt_lca2.lock_read()
 
2324
        self.addCleanup(wt_lca2.unlock)
 
2325
        wt_other = wt.bzrdir.sprout('other', 'F-id').open_workingtree()
 
2326
        wt_other.lock_read()
 
2327
        self.addCleanup(wt_other.unlock)
 
2328
        merge_obj = _mod_merge.Merge3Merger(wt, wt, wt_base,
 
2329
            wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
 
2330
        entries = list(merge_obj._entries_lca())
 
2331
        root_id = wt.path2id('')
 
2332
        self.assertEqual([('foo-id', True,
 
2333
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
2334
                           ((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
 
2335
                           ((False, [False, False]), False, False)),
 
2336
                         ], entries)
 
2337
 
 
2338
    def test_other_reverted_path_to_base(self):
 
2339
        #   A       Path at 'foo'
 
2340
        #  / \
 
2341
        # B   C     Path at 'bar' in B
 
2342
        # |\ /|
 
2343
        # | X |
 
2344
        # |/ \|
 
2345
        # D   E     Path at 'bar'
 
2346
        #     |
 
2347
        #     F     Path at 'foo'
 
2348
        builder = self.get_builder()
 
2349
        builder.build_snapshot('A-id', None,
 
2350
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2351
             ('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
 
2352
        builder.build_snapshot('C-id', ['A-id'], [])
 
2353
        builder.build_snapshot('B-id', ['A-id'],
 
2354
            [('rename', ('foo', 'bar'))])
 
2355
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
2356
            [('rename', ('foo', 'bar'))]) # merge the rename
 
2357
        builder.build_snapshot('F-id', ['E-id'],
 
2358
            [('rename', ('bar', 'foo'))]) # Rename back to BASE
 
2359
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2360
        wt, conflicts = self.do_merge(builder, 'F-id')
 
2361
        self.assertEqual(0, conflicts)
 
2362
        self.assertEqual('foo', wt.id2path('foo-id'))
 
2363
 
 
2364
    def test_other_reverted_content_to_base(self):
 
2365
        builder = self.get_builder()
 
2366
        builder.build_snapshot('A-id', None,
 
2367
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2368
             ('add', (u'foo', 'foo-id', 'file', 'base content\n'))])
 
2369
        builder.build_snapshot('C-id', ['A-id'], [])
 
2370
        builder.build_snapshot('B-id', ['A-id'],
 
2371
            [('modify', ('foo-id', 'B content\n'))])
 
2372
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
2373
            [('modify', ('foo-id', 'B content\n'))]) # merge the content
 
2374
        builder.build_snapshot('F-id', ['E-id'],
 
2375
            [('modify', ('foo-id', 'base content\n'))]) # Revert back to BASE
 
2376
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2377
        wt, conflicts = self.do_merge(builder, 'F-id')
 
2378
        self.assertEqual(0, conflicts)
 
2379
        # TODO: We need to use the per-file graph to properly select a BASE
 
2380
        #       before this will work. Or at least use the LCA trees to find
 
2381
        #       the appropriate content base. (which is B, not A).
 
2382
        self.expectFailure("Merge3Merger doesn't recognize reverted content",
 
2383
            self.assertEqual, 'base content\n', wt.get_file_text('foo-id'))
 
2384
 
 
2385
    def test_other_modified_content(self):
 
2386
        builder = self.get_builder()
 
2387
        builder.build_snapshot('A-id', None,
 
2388
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2389
             ('add', (u'foo', 'foo-id', 'file', 'base content\n'))])
 
2390
        builder.build_snapshot('C-id', ['A-id'], [])
 
2391
        builder.build_snapshot('B-id', ['A-id'],
 
2392
            [('modify', ('foo-id', 'B content\n'))])
 
2393
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
2394
            [('modify', ('foo-id', 'B content\n'))]) # merge the content
 
2395
        builder.build_snapshot('F-id', ['E-id'],
 
2396
            [('modify', ('foo-id', 'F content\n'))]) # Override B content
 
2397
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
2398
        wt, conflicts = self.do_merge(builder, 'F-id')
 
2399
        self.expectFailure("Merge3Merger only uses BASE for content",
 
2400
            self.assertEqual, 'F content\n', wt.get_file_text('foo-id'))
 
2401
        self.assertEqual(0, conflicts)
 
2402
        self.assertEqual('F content\n', wt.get_file_text('foo-id'))
 
2403
 
 
2404
    def test_all_wt(self):
 
2405
        """Check behavior if all trees are Working Trees."""
 
2406
        # The big issue is that entry.revision is None for WorkingTrees. (as is
 
2407
        # entry.text_sha1, etc. So we need to make sure we handle that case
 
2408
        # correctly.
 
2409
        #   A   Content of 'foo', path of 'a'
 
2410
        #   |\
 
2411
        #   B C B modifies content, C renames 'a' => 'b'
 
2412
        #   |X|
 
2413
        #   D E E updates content, renames 'b' => 'c'
 
2414
        builder = self.get_builder()
 
2415
        builder.build_snapshot('A-id', None,
 
2416
            [('add', (u'', 'a-root-id', 'directory', None)),
 
2417
             ('add', (u'a', 'a-id', 'file', 'base content\n')),
 
2418
             ('add', (u'foo', 'foo-id', 'file', 'base content\n'))])
 
2419
        builder.build_snapshot('B-id', ['A-id'],
 
2420
            [('modify', ('foo-id', 'B content\n'))])
 
2421
        builder.build_snapshot('C-id', ['A-id'],
 
2422
            [('rename', ('a', 'b'))])
 
2423
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
2424
            [('rename', ('b', 'c')),
 
2425
             ('modify', ('foo-id', 'E content\n'))])
 
2426
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
2427
            [('rename', ('a', 'b'))]) # merged change
 
2428
        wt_this = self.get_wt_from_builder(builder)
 
2429
        wt_base = wt_this.bzrdir.sprout('base', 'A-id').open_workingtree()
 
2430
        wt_base.lock_read()
 
2431
        self.addCleanup(wt_base.unlock)
 
2432
        wt_lca1 = wt_this.bzrdir.sprout('b-tree', 'B-id').open_workingtree()
 
2433
        wt_lca1.lock_read()
 
2434
        self.addCleanup(wt_lca1.unlock)
 
2435
        wt_lca2 = wt_this.bzrdir.sprout('c-tree', 'C-id').open_workingtree()
 
2436
        wt_lca2.lock_read()
 
2437
        self.addCleanup(wt_lca2.unlock)
 
2438
        wt_other = wt_this.bzrdir.sprout('other', 'E-id').open_workingtree()
 
2439
        wt_other.lock_read()
 
2440
        self.addCleanup(wt_other.unlock)
 
2441
        merge_obj = _mod_merge.Merge3Merger(wt_this, wt_this, wt_base,
 
2442
            wt_other, lca_trees=[wt_lca1, wt_lca2], do_merge=False)
 
2443
        entries = list(merge_obj._entries_lca())
 
2444
        root_id = 'a-root-id'
 
2445
        self.assertEqual([('a-id', False,
 
2446
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
2447
                           ((u'a', [u'a', u'b']), u'c', u'b'),
 
2448
                           ((False, [False, False]), False, False)),
 
2449
                          ('foo-id', True,
 
2450
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
2451
                           ((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
 
2452
                           ((False, [False, False]), False, False)),
 
2453
                         ], entries)
 
2454
 
 
2455
    def test_nested_tree_unmodified(self):
 
2456
        # Tested with a real WT, because BranchBuilder/MemoryTree don't handle
 
2457
        # 'tree-reference'
 
2458
        wt = self.make_branch_and_tree('tree',
 
2459
            format='dirstate-with-subtree')
 
2460
        wt.lock_write()
 
2461
        self.addCleanup(wt.unlock)
 
2462
        sub_tree = self.make_branch_and_tree('tree/sub-tree',
 
2463
            format='dirstate-with-subtree')
 
2464
        wt.set_root_id('a-root-id')
 
2465
        sub_tree.set_root_id('sub-tree-root')
 
2466
        self.build_tree_contents([('tree/sub-tree/file', 'text1')])
 
2467
        sub_tree.add('file')
 
2468
        sub_tree.commit('foo', rev_id='sub-A-id')
 
2469
        wt.add_reference(sub_tree)
 
2470
        wt.commit('set text to 1', rev_id='A-id', recursive=None)
 
2471
        # Now create a criss-cross merge in the parent, without modifying the
 
2472
        # subtree
 
2473
        wt.commit('B', rev_id='B-id', recursive=None)
 
2474
        wt.set_last_revision('A-id')
 
2475
        wt.branch.set_last_revision_info(1, 'A-id')
 
2476
        wt.commit('C', rev_id='C-id', recursive=None)
 
2477
        wt.merge_from_branch(wt.branch, to_revision='B-id')
 
2478
        wt.commit('E', rev_id='E-id', recursive=None)
 
2479
        wt.set_parent_ids(['B-id', 'C-id'])
 
2480
        wt.branch.set_last_revision_info(2, 'B-id')
 
2481
        wt.commit('D', rev_id='D-id', recursive=None)
 
2482
 
 
2483
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2484
            wt, 'E-id')
 
2485
        merger.merge_type = _mod_merge.Merge3Merger
 
2486
        merge_obj = merger.make_merger()
 
2487
        entries = list(merge_obj._entries_lca())
 
2488
        self.assertEqual([], entries)
 
2489
 
 
2490
    def test_nested_tree_subtree_modified(self):
 
2491
        # Tested with a real WT, because BranchBuilder/MemoryTree don't handle
 
2492
        # 'tree-reference'
 
2493
        wt = self.make_branch_and_tree('tree',
 
2494
            format='dirstate-with-subtree')
 
2495
        wt.lock_write()
 
2496
        self.addCleanup(wt.unlock)
 
2497
        sub_tree = self.make_branch_and_tree('tree/sub',
 
2498
            format='dirstate-with-subtree')
 
2499
        wt.set_root_id('a-root-id')
 
2500
        sub_tree.set_root_id('sub-tree-root')
 
2501
        self.build_tree_contents([('tree/sub/file', 'text1')])
 
2502
        sub_tree.add('file')
 
2503
        sub_tree.commit('foo', rev_id='sub-A-id')
 
2504
        wt.add_reference(sub_tree)
 
2505
        wt.commit('set text to 1', rev_id='A-id', recursive=None)
 
2506
        # Now create a criss-cross merge in the parent, without modifying the
 
2507
        # subtree
 
2508
        wt.commit('B', rev_id='B-id', recursive=None)
 
2509
        wt.set_last_revision('A-id')
 
2510
        wt.branch.set_last_revision_info(1, 'A-id')
 
2511
        wt.commit('C', rev_id='C-id', recursive=None)
 
2512
        wt.merge_from_branch(wt.branch, to_revision='B-id')
 
2513
        self.build_tree_contents([('tree/sub/file', 'text2')])
 
2514
        sub_tree.commit('modify contents', rev_id='sub-B-id')
 
2515
        wt.commit('E', rev_id='E-id', recursive=None)
 
2516
        wt.set_parent_ids(['B-id', 'C-id'])
 
2517
        wt.branch.set_last_revision_info(2, 'B-id')
 
2518
        wt.commit('D', rev_id='D-id', recursive=None)
 
2519
 
 
2520
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2521
            wt, 'E-id')
 
2522
        merger.merge_type = _mod_merge.Merge3Merger
 
2523
        merge_obj = merger.make_merger()
 
2524
        entries = list(merge_obj._entries_lca())
 
2525
        # Nothing interesting about this sub-tree, because content changes are
 
2526
        # computed at a higher level
 
2527
        self.assertEqual([], entries)
 
2528
 
 
2529
    def test_nested_tree_subtree_renamed(self):
 
2530
        # Tested with a real WT, because BranchBuilder/MemoryTree don't handle
 
2531
        # 'tree-reference'
 
2532
        wt = self.make_branch_and_tree('tree',
 
2533
            format='dirstate-with-subtree')
 
2534
        wt.lock_write()
 
2535
        self.addCleanup(wt.unlock)
 
2536
        sub_tree = self.make_branch_and_tree('tree/sub',
 
2537
            format='dirstate-with-subtree')
 
2538
        wt.set_root_id('a-root-id')
 
2539
        sub_tree.set_root_id('sub-tree-root')
 
2540
        self.build_tree_contents([('tree/sub/file', 'text1')])
 
2541
        sub_tree.add('file')
 
2542
        sub_tree.commit('foo', rev_id='sub-A-id')
 
2543
        wt.add_reference(sub_tree)
 
2544
        wt.commit('set text to 1', rev_id='A-id', recursive=None)
 
2545
        # Now create a criss-cross merge in the parent, without modifying the
 
2546
        # subtree
 
2547
        wt.commit('B', rev_id='B-id', recursive=None)
 
2548
        wt.set_last_revision('A-id')
 
2549
        wt.branch.set_last_revision_info(1, 'A-id')
 
2550
        wt.commit('C', rev_id='C-id', recursive=None)
 
2551
        wt.merge_from_branch(wt.branch, to_revision='B-id')
 
2552
        wt.rename_one('sub', 'alt_sub')
 
2553
        wt.commit('E', rev_id='E-id', recursive=None)
 
2554
        wt.set_last_revision('B-id')
 
2555
        wt.revert()
 
2556
        wt.set_parent_ids(['B-id', 'C-id'])
 
2557
        wt.branch.set_last_revision_info(2, 'B-id')
 
2558
        wt.commit('D', rev_id='D-id', recursive=None)
 
2559
 
 
2560
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2561
            wt, 'E-id')
 
2562
        merger.merge_type = _mod_merge.Merge3Merger
 
2563
        merge_obj = merger.make_merger()
 
2564
        entries = list(merge_obj._entries_lca())
 
2565
        root_id = 'a-root-id'
 
2566
        self.assertEqual([('sub-tree-root', False,
 
2567
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
2568
                           ((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
 
2569
                           ((False, [False, False]), False, False)),
 
2570
                         ], entries)
 
2571
 
 
2572
    def test_nested_tree_subtree_renamed_and_modified(self):
 
2573
        # Tested with a real WT, because BranchBuilder/MemoryTree don't handle
 
2574
        # 'tree-reference'
 
2575
        wt = self.make_branch_and_tree('tree',
 
2576
            format='dirstate-with-subtree')
 
2577
        wt.lock_write()
 
2578
        self.addCleanup(wt.unlock)
 
2579
        sub_tree = self.make_branch_and_tree('tree/sub',
 
2580
            format='dirstate-with-subtree')
 
2581
        wt.set_root_id('a-root-id')
 
2582
        sub_tree.set_root_id('sub-tree-root')
 
2583
        self.build_tree_contents([('tree/sub/file', 'text1')])
 
2584
        sub_tree.add('file')
 
2585
        sub_tree.commit('foo', rev_id='sub-A-id')
 
2586
        wt.add_reference(sub_tree)
 
2587
        wt.commit('set text to 1', rev_id='A-id', recursive=None)
 
2588
        # Now create a criss-cross merge in the parent, without modifying the
 
2589
        # subtree
 
2590
        wt.commit('B', rev_id='B-id', recursive=None)
 
2591
        wt.set_last_revision('A-id')
 
2592
        wt.branch.set_last_revision_info(1, 'A-id')
 
2593
        wt.commit('C', rev_id='C-id', recursive=None)
 
2594
        wt.merge_from_branch(wt.branch, to_revision='B-id')
 
2595
        self.build_tree_contents([('tree/sub/file', 'text2')])
 
2596
        sub_tree.commit('modify contents', rev_id='sub-B-id')
 
2597
        wt.rename_one('sub', 'alt_sub')
 
2598
        wt.commit('E', rev_id='E-id', recursive=None)
 
2599
        wt.set_last_revision('B-id')
 
2600
        wt.revert()
 
2601
        wt.set_parent_ids(['B-id', 'C-id'])
 
2602
        wt.branch.set_last_revision_info(2, 'B-id')
 
2603
        wt.commit('D', rev_id='D-id', recursive=None)
 
2604
 
 
2605
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
 
2606
            wt, 'E-id')
 
2607
        merger.merge_type = _mod_merge.Merge3Merger
 
2608
        merge_obj = merger.make_merger()
 
2609
        entries = list(merge_obj._entries_lca())
 
2610
        root_id = 'a-root-id'
 
2611
        self.assertEqual([('sub-tree-root', False,
 
2612
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
2613
                           ((u'sub', [u'sub', u'sub']), u'alt_sub', u'sub'),
 
2614
                           ((False, [False, False]), False, False)),
 
2615
                         ], entries)
 
2616
 
 
2617
 
 
2618
class TestLCAMultiWay(tests.TestCase):
 
2619
 
 
2620
    def assertLCAMultiWay(self, expected, base, lcas, other, this,
 
2621
                          allow_overriding_lca=True):
 
2622
        self.assertEqual(expected, _mod_merge.Merge3Merger._lca_multi_way(
 
2623
                                (base, lcas), other, this,
 
2624
                                allow_overriding_lca=allow_overriding_lca))
 
2625
 
 
2626
    def test_other_equal_equal_lcas(self):
 
2627
        """Test when OTHER=LCA and all LCAs are identical."""
 
2628
        self.assertLCAMultiWay('this',
 
2629
            'bval', ['bval', 'bval'], 'bval', 'bval')
 
2630
        self.assertLCAMultiWay('this',
 
2631
            'bval', ['lcaval', 'lcaval'], 'lcaval', 'bval')
 
2632
        self.assertLCAMultiWay('this',
 
2633
            'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', 'bval')
 
2634
        self.assertLCAMultiWay('this',
 
2635
            'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', 'tval')
 
2636
        self.assertLCAMultiWay('this',
 
2637
            'bval', ['lcaval', 'lcaval', 'lcaval'], 'lcaval', None)
 
2638
 
 
2639
    def test_other_equal_this(self):
 
2640
        """Test when other and this are identical."""
 
2641
        self.assertLCAMultiWay('this',
 
2642
            'bval', ['bval', 'bval'], 'oval', 'oval')
 
2643
        self.assertLCAMultiWay('this',
 
2644
            'bval', ['lcaval', 'lcaval'], 'oval', 'oval')
 
2645
        self.assertLCAMultiWay('this',
 
2646
            'bval', ['cval', 'dval'], 'oval', 'oval')
 
2647
        self.assertLCAMultiWay('this',
 
2648
            'bval', [None, 'lcaval'], 'oval', 'oval')
 
2649
        self.assertLCAMultiWay('this',
 
2650
            None, [None, 'lcaval'], 'oval', 'oval')
 
2651
        self.assertLCAMultiWay('this',
 
2652
            None, ['lcaval', 'lcaval'], 'oval', 'oval')
 
2653
        self.assertLCAMultiWay('this',
 
2654
            None, ['cval', 'dval'], 'oval', 'oval')
 
2655
        self.assertLCAMultiWay('this',
 
2656
            None, ['cval', 'dval'], None, None)
 
2657
        self.assertLCAMultiWay('this',
 
2658
            None, ['cval', 'dval', 'eval', 'fval'], 'oval', 'oval')
 
2659
 
 
2660
    def test_no_lcas(self):
 
2661
        self.assertLCAMultiWay('this',
 
2662
            'bval', [], 'bval', 'tval')
 
2663
        self.assertLCAMultiWay('other',
 
2664
            'bval', [], 'oval', 'bval')
 
2665
        self.assertLCAMultiWay('conflict',
 
2666
            'bval', [], 'oval', 'tval')
 
2667
        self.assertLCAMultiWay('this',
 
2668
            'bval', [], 'oval', 'oval')
 
2669
 
 
2670
    def test_lca_supersedes_other_lca(self):
 
2671
        """If one lca == base, the other lca takes precedence"""
 
2672
        self.assertLCAMultiWay('this',
 
2673
            'bval', ['bval', 'lcaval'], 'lcaval', 'tval')
 
2674
        self.assertLCAMultiWay('this',
 
2675
            'bval', ['bval', 'lcaval'], 'lcaval', 'bval')
 
2676
        # This is actually considered a 'revert' because the 'lcaval' in LCAS
 
2677
        # supersedes the BASE val (in the other LCA) but then OTHER reverts it
 
2678
        # back to bval.
 
2679
        self.assertLCAMultiWay('other',
 
2680
            'bval', ['bval', 'lcaval'], 'bval', 'lcaval')
 
2681
        self.assertLCAMultiWay('conflict',
 
2682
            'bval', ['bval', 'lcaval'], 'bval', 'tval')
 
2683
 
 
2684
    def test_other_and_this_pick_different_lca(self):
 
2685
        # OTHER and THIS resolve the lca conflict in different ways
 
2686
        self.assertLCAMultiWay('conflict',
 
2687
            'bval', ['lca1val', 'lca2val'], 'lca1val', 'lca2val')
 
2688
        self.assertLCAMultiWay('conflict',
 
2689
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'lca2val')
 
2690
        self.assertLCAMultiWay('conflict',
 
2691
            'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'lca2val')
 
2692
 
 
2693
    def test_other_in_lca(self):
 
2694
        # OTHER takes a value of one of the LCAs, THIS takes a new value, which
 
2695
        # theoretically supersedes both LCA values and 'wins'
 
2696
        self.assertLCAMultiWay('this',
 
2697
            'bval', ['lca1val', 'lca2val'], 'lca1val', 'newval')
 
2698
        self.assertLCAMultiWay('this',
 
2699
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'newval')
 
2700
        self.assertLCAMultiWay('conflict',
 
2701
            'bval', ['lca1val', 'lca2val'], 'lca1val', 'newval',
 
2702
            allow_overriding_lca=False)
 
2703
        self.assertLCAMultiWay('conflict',
 
2704
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'newval',
 
2705
            allow_overriding_lca=False)
 
2706
        # THIS reverted back to BASE, but that is an explicit supersede of all
 
2707
        # LCAs
 
2708
        self.assertLCAMultiWay('this',
 
2709
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'bval')
 
2710
        self.assertLCAMultiWay('this',
 
2711
            'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'bval')
 
2712
        self.assertLCAMultiWay('conflict',
 
2713
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'bval',
 
2714
            allow_overriding_lca=False)
 
2715
        self.assertLCAMultiWay('conflict',
 
2716
            'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'bval',
 
2717
            allow_overriding_lca=False)
 
2718
 
 
2719
    def test_this_in_lca(self):
 
2720
        # THIS takes a value of one of the LCAs, OTHER takes a new value, which
 
2721
        # theoretically supersedes both LCA values and 'wins'
 
2722
        self.assertLCAMultiWay('other',
 
2723
            'bval', ['lca1val', 'lca2val'], 'oval', 'lca1val')
 
2724
        self.assertLCAMultiWay('other',
 
2725
            'bval', ['lca1val', 'lca2val'], 'oval', 'lca2val')
 
2726
        self.assertLCAMultiWay('conflict',
 
2727
            'bval', ['lca1val', 'lca2val'], 'oval', 'lca1val',
 
2728
            allow_overriding_lca=False)
 
2729
        self.assertLCAMultiWay('conflict',
 
2730
            'bval', ['lca1val', 'lca2val'], 'oval', 'lca2val',
 
2731
            allow_overriding_lca=False)
 
2732
        # OTHER reverted back to BASE, but that is an explicit supersede of all
 
2733
        # LCAs
 
2734
        self.assertLCAMultiWay('other',
 
2735
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'bval', 'lca3val')
 
2736
        self.assertLCAMultiWay('conflict',
 
2737
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'bval', 'lca3val',
 
2738
            allow_overriding_lca=False)
 
2739
 
 
2740
    def test_all_differ(self):
 
2741
        self.assertLCAMultiWay('conflict',
 
2742
            'bval', ['lca1val', 'lca2val'], 'oval', 'tval')
 
2743
        self.assertLCAMultiWay('conflict',
 
2744
            'bval', ['lca1val', 'lca2val', 'lca2val'], 'oval', 'tval')
 
2745
        self.assertLCAMultiWay('conflict',
 
2746
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'oval', 'tval')