/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/tests/test_merge.py

  • Committer: John Arbash Meinel
  • Date: 2008-07-31 19:12:59 UTC
  • mto: (3697.7.4 1.7)
  • mto: This revision was merged to the branch mainline in revision 3748.
  • Revision ID: john@arbash-meinel.com-20080731191259-81qy04c3h4bxir0s
Several updates.

Add a document describing the merge algorithm, and the rationale
for the different steps.
Change the sha1 algorithm to use get_file_sha1() rather than assuming the
inventory entries text_sha1 value is correct. (It is always None for working trees.)
For sha1-values specifically, don't allow overruling an LCA value when one tip
seems to supersede the other's value. We will still do this for names and parent_ids.
Add tests that we conflict if lcas disagree and tips do not have the same content,
even if one of them is an lca value, and one is newer.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1238
1238
        self.assertIsInstance(merge_obj, UnsupportedLCATreesMerger)
1239
1239
        self.assertFalse('lca_trees' in merge_obj.kwargs)
1240
1240
 
 
1241
# XXX: Thing left to test:
 
1242
#       Double-criss-cross merge, the ultimate base value is different from the
 
1243
#       intermediate.
 
1244
#         A    value 'foo'
 
1245
#         |\
 
1246
#         B C  B value 'bar', C = 'foo'
 
1247
#         |X|
 
1248
#         D E  D = 'bar', E supersedes to 'bing'
 
1249
#         |X|
 
1250
#         F G  F = 'bing', G supersedes to 'barry'
 
1251
#
 
1252
#       In this case, we technically should not care about the value 'bar' for
 
1253
#       D, because it was clearly superseded by E's 'bing'. The
 
1254
#       per-file/attribute graph would actually look like:
 
1255
#         A
 
1256
#         |
 
1257
#         B
 
1258
#         |
 
1259
#         E
 
1260
#         |
 
1261
#         G
 
1262
#
 
1263
#       Because the other side of the merge never modifies the value, it just
 
1264
#       takes the value from the merge.
 
1265
#
 
1266
#       ATM I expect this to fail because we will prune 'foo' from the LCAs,
 
1267
#       and not 'bar'. I don't know if this is strictly a problem, as it is a
 
1268
#       distinctly edge case.
 
1269
#
 
1270
#       Another incorrect resolution from the same basic flaw:
 
1271
#         A    value 'foo'
 
1272
#         |\
 
1273
#         B C  B value 'bar', C = 'foo'
 
1274
#         |X|
 
1275
#         D E  D = 'bar', E reverts to 'foo'
 
1276
#         |X|
 
1277
#         F G  F = 'bar', G reverts to 'foo'
 
1278
 
1241
1279
 
1242
1280
class TestMergerEntriesLCA(TestMergerBase):
1243
1281
 
1383
1421
                           ((False, [False, False]), None, False)),
1384
1422
                         ], entries)
1385
1423
 
 
1424
    def test_not_in_other_or_lca(self):
 
1425
        #       A    base, introduces 'foo'
 
1426
        #       |\ 
 
1427
        #       B C  B nothing, C deletes foo
 
1428
        #       |X|
 
1429
        #       D E  D restores foo (same as B), E leaves it deleted
 
1430
        # We should emit an entry for this
 
1431
        builder = self.get_builder()
 
1432
        builder.build_snapshot('A-id', None,
 
1433
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1434
             ('add', (u'foo', 'foo-id', 'file', 'content\n'))])
 
1435
        builder.build_snapshot('B-id', ['A-id'], [])
 
1436
        builder.build_snapshot('C-id', ['A-id'],
 
1437
            [('unversion', 'foo-id')])
 
1438
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1439
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1440
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1441
 
 
1442
        entries = list(merge_obj._entries_lca())
 
1443
        root_id = 'a-root-id'
 
1444
        self.assertEqual([('foo-id', True,
 
1445
                           ((root_id, [root_id, None]), None, root_id),
 
1446
                           ((u'foo', [u'foo', None]), None, 'foo'),
 
1447
                           ((False, [False, None]), None, False)),
 
1448
                         ], entries)
 
1449
 
1386
1450
    def test_only_in_one_lca(self):
1387
1451
        builder = self.get_builder()
1388
1452
        builder.build_snapshot('A-id', None,
1422
1486
                           ((None, [None, None]), False, None)),
1423
1487
                         ], entries)
1424
1488
 
 
1489
    def test_one_lca_supersedes(self):
 
1490
        # One LCA supersede's the other LCA's last modified value, but the
 
1491
        # value is not the same as BASE.
 
1492
        #       A    base, introduces 'foo', last mod A
 
1493
        #       |\ 
 
1494
        #       B C  B modifies 'foo' (mod B), C does nothing (mod A)
 
1495
        #       |X|
 
1496
        #       D E  D does nothing (mod B), E updates 'foo' (mod E)
 
1497
        #       |X|
 
1498
        #       F G  F updates 'foo' (mod F). G does nothing (mod E)
 
1499
        #
 
1500
        #   At this point, G should not be considered to modify 'foo', even
 
1501
        #   though its LCAs disagree. This is because the modification in E
 
1502
        #   completely supersedes the value in D.
 
1503
        builder = self.get_builder()
 
1504
        builder.build_snapshot('A-id', None,
 
1505
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1506
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1507
        builder.build_snapshot('C-id', ['A-id'], [])
 
1508
        builder.build_snapshot('B-id', ['A-id'],
 
1509
            [('modify', ('foo-id', 'B content\n'))])
 
1510
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1511
        builder.build_snapshot('E-id', ['C-id', 'B-id'],
 
1512
            [('modify', ('foo-id', 'E content\n'))])
 
1513
        builder.build_snapshot('G-id', ['E-id', 'D-id'], [])
 
1514
        builder.build_snapshot('F-id', ['D-id', 'E-id'],
 
1515
            [('modify', ('foo-id', 'F content\n'))])
 
1516
        merge_obj = self.make_merge_obj(builder, 'G-id')
 
1517
 
 
1518
        self.assertEqual([], list(merge_obj._entries_lca()))
 
1519
 
 
1520
    def test_both_sides_revert(self):
 
1521
        # Both sides of a criss-cross revert the text to the lca
 
1522
        #       A    base, introduces 'foo'
 
1523
        #       |\ 
 
1524
        #       B C  B modifies 'foo', C modifies 'foo'
 
1525
        #       |X|
 
1526
        #       D E  D reverts to B, E reverts to C
 
1527
        # This should conflict
 
1528
        builder = self.get_builder()
 
1529
        builder.build_snapshot('A-id', None,
 
1530
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1531
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1532
        builder.build_snapshot('B-id', ['A-id'],
 
1533
            [('modify', ('foo-id', 'B content\n'))])
 
1534
        builder.build_snapshot('C-id', ['A-id'],
 
1535
            [('modify', ('foo-id', 'C content\n'))])
 
1536
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1537
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1538
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1539
 
 
1540
        entries = list(merge_obj._entries_lca())
 
1541
        root_id = 'a-root-id'
 
1542
        self.assertEqual([('foo-id', True,
 
1543
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1544
                           ((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
 
1545
                           ((False, [False, False]), False, False)),
 
1546
                         ], entries)
 
1547
 
 
1548
    def test_different_lca_resolve_one_side_updates_content(self):
 
1549
        # Both sides converge, but then one side updates the text.
 
1550
        #       A    base, introduces 'foo'
 
1551
        #       |\ 
 
1552
        #       B C  B modifies 'foo', C modifies 'foo'
 
1553
        #       |X|
 
1554
        #       D E  D reverts to B, E reverts to C
 
1555
        #       |
 
1556
        #       F    F updates to a new value
 
1557
        # We need to emit an entry for 'foo', because D & E differed on the
 
1558
        # merge resolution
 
1559
        builder = self.get_builder()
 
1560
        builder.build_snapshot('A-id', None,
 
1561
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1562
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1563
        builder.build_snapshot('B-id', ['A-id'],
 
1564
            [('modify', ('foo-id', 'B content\n'))])
 
1565
        builder.build_snapshot('C-id', ['A-id'],
 
1566
            [('modify', ('foo-id', 'C content\n'))])
 
1567
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1568
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1569
        builder.build_snapshot('F-id', ['D-id'],
 
1570
            [('modify', ('foo-id', 'F content\n'))])
 
1571
        merge_obj = self.make_merge_obj(builder, 'E-id')
 
1572
 
 
1573
        entries = list(merge_obj._entries_lca())
 
1574
        root_id = 'a-root-id'
 
1575
        self.assertEqual([('foo-id', True,
 
1576
                           ((root_id, [root_id, root_id]), root_id, root_id),
 
1577
                           ((u'foo', [u'foo', u'foo']), u'foo', u'foo'),
 
1578
                           ((False, [False, False]), False, False)),
 
1579
                         ], entries)
 
1580
 
 
1581
    def test_same_lca_resolution_one_side_updates_content(self):
 
1582
        # Both sides converge, but then one side updates the text.
 
1583
        #       A    base, introduces 'foo'
 
1584
        #       |\ 
 
1585
        #       B C  B modifies 'foo', C modifies 'foo'
 
1586
        #       |X|
 
1587
        #       D E  D and E use C's value
 
1588
        #       |
 
1589
        #       F    F updates to a new value
 
1590
        # I think it is a bug that this conflicts, but we don't have a way to
 
1591
        # detect otherwise. And because of:
 
1592
        #   test_different_lca_resolve_one_side_updates_content
 
1593
        # We need to conflict.
 
1594
 
 
1595
        builder = self.get_builder()
 
1596
        builder.build_snapshot('A-id', None,
 
1597
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1598
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1599
        builder.build_snapshot('B-id', ['A-id'],
 
1600
            [('modify', ('foo-id', 'B content\n'))])
 
1601
        builder.build_snapshot('C-id', ['A-id'],
 
1602
            [('modify', ('foo-id', 'C content\n'))])
 
1603
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1604
        builder.build_snapshot('D-id', ['B-id', 'C-id'],
 
1605
            [('modify', ('foo-id', 'C content\n'))]) # Same as E
 
1606
        builder.build_snapshot('F-id', ['D-id'],
 
1607
            [('modify', ('foo-id', 'F content\n'))])
 
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.expectFailure("We don't detect that LCA resolution was the"
 
1613
                           " same on both sides",
 
1614
            self.assertEqual, [], entries)
 
1615
 
1425
1616
    def test_only_path_changed(self):
1426
1617
        builder = self.get_builder()
1427
1618
        builder.build_snapshot('A-id', None,
1761
1952
        self.assertEqual('foo-id', wt.path2id('foo'))
1762
1953
        self.assertEqual('bar', wt.get_symlink_target('foo-id'))
1763
1954
 
 
1955
    def test_both_sides_revert(self):
 
1956
        # Both sides of a criss-cross revert the text to the lca
 
1957
        #       A    base, introduces 'foo'
 
1958
        #       |\ 
 
1959
        #       B C  B modifies 'foo', C modifies 'foo'
 
1960
        #       |X|
 
1961
        #       D E  D reverts to B, E reverts to C
 
1962
        # This should conflict
 
1963
        # This must be done with a real WorkingTree, because normally their
 
1964
        # inventory contains "None" rather than a real sha1
 
1965
        builder = self.get_builder()
 
1966
        builder.build_snapshot('A-id', None,
 
1967
            [('add', (u'', 'a-root-id', 'directory', None)),
 
1968
             ('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
 
1969
        builder.build_snapshot('B-id', ['A-id'],
 
1970
            [('modify', ('foo-id', 'B content\n'))])
 
1971
        builder.build_snapshot('C-id', ['A-id'],
 
1972
            [('modify', ('foo-id', 'C content\n'))])
 
1973
        builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
 
1974
        builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
 
1975
        wt, conflicts = self.do_merge(builder, 'E-id')
 
1976
        self.assertEqual(1, conflicts)
 
1977
        self.assertEqualDiff('<<<<<<< TREE\n'
 
1978
                             'B content\n'
 
1979
                             '=======\n'
 
1980
                             'C content\n'
 
1981
                             '>>>>>>> MERGE-SOURCE\n',
 
1982
                             wt.get_file_text('foo-id'))
 
1983
 
1764
1984
    def test_modified_symlink(self):
1765
1985
        self.requireFeature(tests.SymlinkFeature)
1766
1986
        #   A       Create symlink foo => bar
1983
2203
 
1984
2204
class TestLCAMultiWay(tests.TestCase):
1985
2205
 
1986
 
    def assertLCAMultiWay(self, expected, base, lcas, other, this):
 
2206
    def assertLCAMultiWay(self, expected, base, lcas, other, this,
 
2207
                          allow_overriding_lca=True):
1987
2208
        self.assertEqual(expected, _mod_merge.Merge3Merger._lca_multi_way(
1988
 
                                        (base, lcas), other, this))
 
2209
                                (base, lcas), other, this,
 
2210
                                allow_overriding_lca=allow_overriding_lca))
1989
2211
 
1990
2212
    def test_other_equal_equal_lcas(self):
1991
2213
        """Test when OTHER=LCA and all LCAs are identical."""
2061
2283
            'bval', ['lca1val', 'lca2val'], 'lca1val', 'newval')
2062
2284
        self.assertLCAMultiWay('this',
2063
2285
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'newval')
 
2286
        self.assertLCAMultiWay('conflict',
 
2287
            'bval', ['lca1val', 'lca2val'], 'lca1val', 'newval',
 
2288
            allow_overriding_lca=False)
 
2289
        self.assertLCAMultiWay('conflict',
 
2290
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'newval',
 
2291
            allow_overriding_lca=False)
2064
2292
        # THIS reverted back to BASE, but that is an explicit supersede of all
2065
2293
        # LCAs
2066
2294
        self.assertLCAMultiWay('this',
2067
2295
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'bval')
2068
2296
        self.assertLCAMultiWay('this',
2069
2297
            'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'bval')
 
2298
        self.assertLCAMultiWay('conflict',
 
2299
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'lca1val', 'bval',
 
2300
            allow_overriding_lca=False)
 
2301
        self.assertLCAMultiWay('conflict',
 
2302
            'bval', ['lca1val', 'lca2val', 'bval'], 'lca1val', 'bval',
 
2303
            allow_overriding_lca=False)
2070
2304
 
2071
2305
    def test_this_in_lca(self):
2072
2306
        # THIS takes a value of one of the LCAs, OTHER takes a new value, which
2075
2309
            'bval', ['lca1val', 'lca2val'], 'oval', 'lca1val')
2076
2310
        self.assertLCAMultiWay('other',
2077
2311
            'bval', ['lca1val', 'lca2val'], 'oval', 'lca2val')
 
2312
        self.assertLCAMultiWay('conflict',
 
2313
            'bval', ['lca1val', 'lca2val'], 'oval', 'lca1val',
 
2314
            allow_overriding_lca=False)
 
2315
        self.assertLCAMultiWay('conflict',
 
2316
            'bval', ['lca1val', 'lca2val'], 'oval', 'lca2val',
 
2317
            allow_overriding_lca=False)
2078
2318
        # OTHER reverted back to BASE, but that is an explicit supersede of all
2079
2319
        # LCAs
2080
2320
        self.assertLCAMultiWay('other',
2081
2321
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'bval', 'lca3val')
 
2322
        self.assertLCAMultiWay('conflict',
 
2323
            'bval', ['lca1val', 'lca2val', 'lca3val'], 'bval', 'lca3val',
 
2324
            allow_overriding_lca=False)
2082
2325
 
2083
2326
    def test_all_differ(self):
2084
2327
        self.assertLCAMultiWay('conflict',