384
417
'>>>>>>> MERGE-SOURCE\n',
420
def test_merge_reverse_revision_range(self):
421
tree = self.make_branch_and_tree(".")
423
self.addCleanup(tree.unlock)
424
self.build_tree(['a'])
426
first_rev = tree.commit("added a")
427
merger = _mod_merge.Merger.from_revision_ids(tree,
428
_mod_revision.NULL_REVISION,
430
merger.merge_type = _mod_merge.Merge3Merger
431
merger.interesting_files = 'a'
432
conflict_count = merger.do_merge()
433
self.assertEqual(0, conflict_count)
435
self.assertPathDoesNotExist("a")
437
self.assertPathExists("a")
387
439
def test_make_merger(self):
388
440
this_tree = self.make_branch_and_tree('this')
389
441
this_tree.commit('rev1', rev_id='rev1')
390
other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
442
other_tree = this_tree.controldir.sprout('other').open_workingtree()
391
443
this_tree.commit('rev2', rev_id='rev2a')
392
444
other_tree.commit('rev2', rev_id='rev2b')
393
445
this_tree.lock_write()
394
446
self.addCleanup(this_tree.unlock)
395
merger = _mod_merge.Merger.from_revision_ids(None,
447
merger = _mod_merge.Merger.from_revision_ids(
396
448
this_tree, 'rev2b', other_branch=other_tree.branch)
397
449
merger.merge_type = _mod_merge.Merge3Merger
398
450
tree_merger = merger.make_merger()
399
451
self.assertIs(_mod_merge.Merge3Merger, tree_merger.__class__)
400
self.assertEqual('rev2b', tree_merger.other_tree.get_revision_id())
401
self.assertEqual('rev1', tree_merger.base_tree.get_revision_id())
452
self.assertEqual('rev2b',
453
tree_merger.other_tree.get_revision_id())
454
self.assertEqual('rev1',
455
tree_merger.base_tree.get_revision_id())
456
self.assertEqual(other_tree.branch, tree_merger.other_branch)
403
458
def test_make_preview_transform(self):
404
459
this_tree = self.make_branch_and_tree('this')
405
460
self.build_tree_contents([('this/file', '1\n')])
406
461
this_tree.add('file', 'file-id')
407
462
this_tree.commit('rev1', rev_id='rev1')
408
other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
463
other_tree = this_tree.controldir.sprout('other').open_workingtree()
409
464
self.build_tree_contents([('this/file', '1\n2a\n')])
410
465
this_tree.commit('rev2', rev_id='rev2a')
411
466
self.build_tree_contents([('other/file', '2b\n1\n')])
412
467
other_tree.commit('rev2', rev_id='rev2b')
413
468
this_tree.lock_write()
414
469
self.addCleanup(this_tree.unlock)
415
merger = _mod_merge.Merger.from_revision_ids(None,
470
merger = _mod_merge.Merger.from_revision_ids(
416
471
this_tree, 'rev2b', other_branch=other_tree.branch)
417
472
merger.merge_type = _mod_merge.Merge3Merger
418
473
tree_merger = merger.make_merger()
419
474
tt = tree_merger.make_preview_transform()
420
475
self.addCleanup(tt.finalize)
421
476
preview_tree = tt.get_preview_tree()
422
tree_file = this_tree.get_file('file-id')
477
tree_file = this_tree.get_file('file')
424
479
self.assertEqual('1\n2a\n', tree_file.read())
426
481
tree_file.close()
427
preview_file = preview_tree.get_file('file-id')
482
preview_file = preview_tree.get_file('file')
429
484
self.assertEqual('2b\n1\n2a\n', preview_file.read())
1354
1453
# G modifies 'bar'
1356
1455
builder = self.get_builder()
1357
builder.build_snapshot('A-id', None,
1358
[('add', (u'', 'a-root-id', 'directory', None))])
1359
builder.build_snapshot('B-id', ['A-id'],
1360
[('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
1361
builder.build_snapshot('C-id', ['A-id'],
1362
[('add', (u'bar', 'bar-id', 'file', 'd\ne\nf\n'))])
1363
builder.build_snapshot('D-id', ['B-id', 'C-id'],
1364
[('add', (u'bar', 'bar-id', 'file', 'd\ne\nf\n'))])
1365
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1366
[('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
1367
builder.build_snapshot('G-id', ['E-id', 'D-id'],
1368
[('modify', (u'bar-id', 'd\ne\nf\nG\n'))])
1369
builder.build_snapshot('F-id', ['D-id', 'E-id'], [])
1456
builder.build_snapshot(None,
1457
[('add', (u'', 'a-root-id', 'directory', None))],
1459
builder.build_snapshot(['A-id'],
1460
[('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))],
1462
builder.build_snapshot(['A-id'],
1463
[('add', (u'bar', 'bar-id', 'file', 'd\ne\nf\n'))],
1465
builder.build_snapshot(['B-id', 'C-id'],
1466
[('add', (u'bar', 'bar-id', 'file', 'd\ne\nf\n'))],
1468
builder.build_snapshot(['C-id', 'B-id'],
1469
[('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))],
1471
builder.build_snapshot(['E-id', 'D-id'],
1472
[('modify', (u'bar-id', 'd\ne\nf\nG\n'))],
1474
builder.build_snapshot(['D-id', 'E-id'], [], revision_id='F-id')
1370
1475
merge_obj = self.make_merge_obj(builder, 'G-id')
1372
1477
self.assertEqual(['D-id', 'E-id'], [t.get_revision_id()
1576
1701
# though its LCAs disagree. This is because the modification in E
1577
1702
# completely supersedes the value in D.
1578
1703
builder = self.get_builder()
1579
builder.build_snapshot('A-id', None,
1704
builder.build_snapshot(None,
1580
1705
[('add', (u'', 'a-root-id', 'directory', None)),
1581
('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
1582
builder.build_snapshot('C-id', ['A-id'], [])
1583
builder.build_snapshot('B-id', ['A-id'],
1584
[('modify', ('foo-id', 'B content\n'))])
1585
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1586
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1587
[('modify', ('foo-id', 'E content\n'))])
1588
builder.build_snapshot('G-id', ['E-id', 'D-id'], [])
1589
builder.build_snapshot('F-id', ['D-id', 'E-id'],
1590
[('modify', ('foo-id', 'F content\n'))])
1706
('add', (u'foo', 'foo-id', 'file', 'A content\n'))],
1708
builder.build_snapshot(['A-id'], [], revision_id='C-id')
1709
builder.build_snapshot(['A-id'],
1710
[('modify', ('foo-id', 'B content\n'))],
1712
builder.build_snapshot(['B-id', 'C-id'], [], revision_id='D-id')
1713
builder.build_snapshot(['C-id', 'B-id'],
1714
[('modify', ('foo-id', 'E content\n'))],
1716
builder.build_snapshot(['E-id', 'D-id'], [], revision_id='G-id')
1717
builder.build_snapshot(['D-id', 'E-id'],
1718
[('modify', ('foo-id', 'F content\n'))],
1591
1720
merge_obj = self.make_merge_obj(builder, 'G-id')
1593
1722
self.assertEqual([], list(merge_obj._entries_lca()))
1622
1751
# aren't supporting it yet.
1624
1753
builder = self.get_builder()
1625
builder.build_snapshot('A-id', None,
1754
builder.build_snapshot(None,
1626
1755
[('add', (u'', 'a-root-id', 'directory', None)),
1627
('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
1628
builder.build_snapshot('C-id', ['A-id'], [])
1629
builder.build_snapshot('B-id', ['A-id'],
1630
[('rename', ('foo', 'bar'))])
1631
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1632
builder.build_snapshot('E-id', ['C-id', 'B-id'],
1633
[('rename', ('foo', 'bing'))]) # override to bing
1634
builder.build_snapshot('G-id', ['E-id', 'D-id'],
1635
[('rename', ('bing', 'barry'))]) # override to barry
1636
builder.build_snapshot('F-id', ['D-id', 'E-id'],
1637
[('rename', ('bar', 'bing'))]) # Merge in E's change
1756
('add', (u'foo', 'foo-id', 'file', 'A content\n'))],
1758
builder.build_snapshot(['A-id'], [], revision_id='C-id')
1759
builder.build_snapshot(['A-id'],
1760
[('rename', ('foo', 'bar'))],
1762
builder.build_snapshot(['B-id', 'C-id'], [], revision_id='D-id')
1763
builder.build_snapshot(['C-id', 'B-id'],
1764
[('rename', ('foo', 'bing'))],
1765
revision_id='E-id') # override to bing
1766
builder.build_snapshot(['E-id', 'D-id'],
1767
[('rename', ('bing', 'barry'))],
1768
revision_id='G-id') # override to barry
1769
builder.build_snapshot(['D-id', 'E-id'],
1770
[('rename', ('bar', 'bing'))],
1771
revision_id='F-id') # Merge in E's change
1638
1772
merge_obj = self.make_merge_obj(builder, 'G-id')
1640
1774
self.expectFailure("We don't do an actual heads() check on lca values,"
1655
1789
# be pruned from the LCAs, even though it was newly introduced by E
1656
1790
# (superseding B).
1657
1791
builder = self.get_builder()
1658
builder.build_snapshot('A-id', None,
1792
builder.build_snapshot(None,
1659
1793
[('add', (u'', 'a-root-id', 'directory', None)),
1660
('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
1661
builder.build_snapshot('C-id', ['A-id'], [])
1662
builder.build_snapshot('B-id', ['A-id'],
1663
[('rename', ('foo', 'bar'))])
1664
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
1665
builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
1666
builder.build_snapshot('G-id', ['E-id', 'D-id'],
1667
[('rename', ('foo', 'bar'))])
1668
builder.build_snapshot('F-id', ['D-id', 'E-id'],
1669
[('rename', ('bar', 'bing'))]) # should end up conflicting
1794
('add', (u'foo', 'foo-id', 'file', 'A content\n'))],
1796
builder.build_snapshot(['A-id'], [], revision_id='C-id')
1797
builder.build_snapshot(['A-id'],
1798
[('rename', ('foo', 'bar'))],
1800
builder.build_snapshot(['B-id', 'C-id'], [], revision_id='D-id')
1801
builder.build_snapshot(['C-id', 'B-id'], [], revision_id='E-id')
1802
builder.build_snapshot(['E-id', 'D-id'],
1803
[('rename', ('foo', 'bar'))],
1805
builder.build_snapshot(['D-id', 'E-id'],
1806
[('rename', ('bar', 'bing'))],
1807
revision_id='F-id') # should end up conflicting
1670
1808
merge_obj = self.make_merge_obj(builder, 'G-id')
1672
1810
entries = list(merge_obj._entries_lca())
1755
1900
# We need to conflict.
1757
1902
builder = self.get_builder()
1758
builder.build_snapshot('A-id', None,
1903
builder.build_snapshot(None,
1759
1904
[('add', (u'', 'a-root-id', 'directory', None)),
1760
('add', (u'foo', 'foo-id', 'file', 'A content\n'))])
1761
builder.build_snapshot('B-id', ['A-id'],
1762
[('modify', ('foo-id', 'B content\n'))])
1763
builder.build_snapshot('C-id', ['A-id'],
1764
[('modify', ('foo-id', 'C content\n'))])
1765
builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
1766
builder.build_snapshot('D-id', ['B-id', 'C-id'],
1767
[('modify', ('foo-id', 'C content\n'))]) # Same as E
1768
builder.build_snapshot('F-id', ['D-id'],
1769
[('modify', ('foo-id', 'F content\n'))])
1905
('add', (u'foo', 'foo-id', 'file', 'A content\n'))],
1907
builder.build_snapshot(['A-id'],
1908
[('modify', ('foo-id', 'B content\n'))],
1910
builder.build_snapshot(['A-id'],
1911
[('modify', ('foo-id', 'C content\n'))],
1913
builder.build_snapshot(['C-id', 'B-id'], [], revision_id='E-id')
1914
builder.build_snapshot(['B-id', 'C-id'],
1915
[('modify', ('foo-id', 'C content\n'))],
1916
revision_id='D-id') # Same as E
1917
builder.build_snapshot(['D-id'],
1918
[('modify', ('foo-id', 'F content\n'))],
1770
1920
merge_obj = self.make_merge_obj(builder, 'E-id')
1772
1922
entries = list(merge_obj._entries_lca())
1883
2046
def test_interesting_file_in_base(self):
1884
2047
# This renamed the file, but it should still match the entry in BASE
1885
2048
builder = self.get_builder()
1886
builder.build_snapshot('A-id', None,
2049
builder.build_snapshot(None,
1887
2050
[('add', (u'', 'a-root-id', 'directory', None)),
1888
2051
('add', (u'a', 'a-id', 'file', 'content\n')),
1889
('add', (u'c', 'c-id', 'file', 'content\n'))])
1890
builder.build_snapshot('B-id', ['A-id'],
1891
[('rename', ('c', 'b'))])
1892
builder.build_snapshot('C-id', ['A-id'],
1893
[('rename', ('c', 'b'))])
1894
builder.build_snapshot('E-id', ['C-id', 'B-id'],
2052
('add', (u'c', 'c-id', 'file', 'content\n'))],
2054
builder.build_snapshot(['A-id'],
2055
[('rename', ('c', 'b'))],
2057
builder.build_snapshot(['A-id'],
2058
[('rename', ('c', 'b'))],
2060
builder.build_snapshot(['C-id', 'B-id'],
1895
2061
[('modify', ('a-id', 'new-content\n')),
1896
('modify', ('c-id', 'new-content\n'))])
1897
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
2062
('modify', ('c-id', 'new-content\n'))],
2064
builder.build_snapshot(['B-id', 'C-id'], [], revision_id='D-id')
1898
2065
merge_obj = self.make_merge_obj(builder, 'E-id',
1899
2066
interesting_files=['c'])
1900
2067
entries = list(merge_obj._entries_lca())
1908
2075
def test_interesting_file_in_lca(self):
1909
2076
# This renamed the file, but it should still match the entry in LCA
1910
2077
builder = self.get_builder()
1911
builder.build_snapshot('A-id', None,
2078
builder.build_snapshot(None,
1912
2079
[('add', (u'', 'a-root-id', 'directory', None)),
1913
2080
('add', (u'a', 'a-id', 'file', 'content\n')),
1914
('add', (u'b', 'b-id', 'file', 'content\n'))])
1915
builder.build_snapshot('B-id', ['A-id'],
1916
[('rename', ('b', 'c'))])
1917
builder.build_snapshot('C-id', ['A-id'], [])
1918
builder.build_snapshot('E-id', ['C-id', 'B-id'],
2081
('add', (u'b', 'b-id', 'file', 'content\n'))],
2083
builder.build_snapshot(['A-id'],
2084
[('rename', ('b', 'c'))], revision_id='B-id')
2085
builder.build_snapshot(['A-id'], [], revision_id='C-id')
2086
builder.build_snapshot(['C-id', 'B-id'],
1919
2087
[('modify', ('a-id', 'new-content\n')),
1920
('modify', ('b-id', 'new-content\n'))])
1921
builder.build_snapshot('D-id', ['B-id', 'C-id'],
1922
[('rename', ('c', 'b'))])
2088
('modify', ('b-id', 'new-content\n'))],
2090
builder.build_snapshot(['B-id', 'C-id'],
2091
[('rename', ('c', 'b'))], revision_id='D-id')
1923
2092
merge_obj = self.make_merge_obj(builder, 'E-id',
1924
2093
interesting_files=['c'])
1925
2094
entries = list(merge_obj._entries_lca())
1979
2149
def do_merge(self, builder, other_revision_id):
1980
2150
wt = self.get_wt_from_builder(builder)
1981
merger = _mod_merge.Merger.from_revision_ids(None,
2151
merger = _mod_merge.Merger.from_revision_ids(
1982
2152
wt, other_revision_id)
1983
2153
merger.merge_type = _mod_merge.Merge3Merger
1984
2154
return wt, merger.do_merge()
1986
2156
def test_simple_lca(self):
1987
2157
builder = self.get_builder()
1988
builder.build_snapshot('A-id', None,
2158
builder.build_snapshot(None,
1989
2159
[('add', (u'', 'a-root-id', 'directory', None)),
1990
('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))])
1991
builder.build_snapshot('C-id', ['A-id'], [])
1992
builder.build_snapshot('B-id', ['A-id'], [])
1993
builder.build_snapshot('E-id', ['C-id', 'B-id'], [])
1994
builder.build_snapshot('D-id', ['B-id', 'C-id'],
1995
[('modify', ('a-id', 'a\nb\nc\nd\ne\nf\n'))])
2160
('add', (u'a', 'a-id', 'file', 'a\nb\nc\n'))],
2162
builder.build_snapshot(['A-id'], [], revision_id='C-id')
2163
builder.build_snapshot(['A-id'], [], revision_id='B-id')
2164
builder.build_snapshot(['C-id', 'B-id'], [], revision_id='E-id')
2165
builder.build_snapshot(['B-id', 'C-id'],
2166
[('modify', ('a-id', 'a\nb\nc\nd\ne\nf\n'))],
1996
2168
wt, conflicts = self.do_merge(builder, 'E-id')
1997
2169
self.assertEqual(0, conflicts)
1998
2170
# The merge should have simply update the contents of 'a'
1999
self.assertEqual('a\nb\nc\nd\ne\nf\n', wt.get_file_text('a-id'))
2171
self.assertEqual('a\nb\nc\nd\ne\nf\n', wt.get_file_text('a'))
2001
2173
def test_conflict_without_lca(self):
2002
2174
# This test would cause a merge conflict, unless we use the lca trees
2012
2184
# F Path at 'baz' in F, which supersedes 'bar' and 'foo'
2013
2185
builder = self.get_builder()
2014
builder.build_snapshot('A-id', None,
2186
builder.build_snapshot(None,
2015
2187
[('add', (u'', 'a-root-id', 'directory', None)),
2016
('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
2017
builder.build_snapshot('C-id', ['A-id'], [])
2018
builder.build_snapshot('B-id', ['A-id'],
2019
[('rename', ('foo', 'bar'))])
2020
builder.build_snapshot('E-id', ['C-id', 'B-id'], # merge the rename
2021
[('rename', ('foo', 'bar'))])
2022
builder.build_snapshot('F-id', ['E-id'],
2023
[('rename', ('bar', 'baz'))])
2024
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
2188
('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))],
2190
builder.build_snapshot(['A-id'], [], revision_id='C-id')
2191
builder.build_snapshot(['A-id'],
2192
[('rename', ('foo', 'bar'))], revision_id='B-id', )
2193
builder.build_snapshot(['C-id', 'B-id'], # merge the rename
2194
[('rename', ('foo', 'bar'))], revision_id='E-id')
2195
builder.build_snapshot(['E-id'],
2196
[('rename', ('bar', 'baz'))], revision_id='F-id')
2197
builder.build_snapshot(['B-id', 'C-id'], [], revision_id='D-id')
2025
2198
wt, conflicts = self.do_merge(builder, 'F-id')
2026
2199
self.assertEqual(0, conflicts)
2027
2200
# The merge should simply recognize that the final rename takes
2436
2614
# F Path at 'foo'
2437
2615
builder = self.get_builder()
2438
builder.build_snapshot('A-id', None,
2616
builder.build_snapshot(None,
2439
2617
[('add', (u'', 'a-root-id', 'directory', None)),
2440
('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))])
2441
builder.build_snapshot('C-id', ['A-id'], [])
2442
builder.build_snapshot('B-id', ['A-id'],
2443
[('rename', ('foo', 'bar'))])
2444
builder.build_snapshot('E-id', ['C-id', 'B-id'],
2445
[('rename', ('foo', 'bar'))]) # merge the rename
2446
builder.build_snapshot('F-id', ['E-id'],
2447
[('rename', ('bar', 'foo'))]) # Rename back to BASE
2448
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
2618
('add', (u'foo', 'foo-id', 'file', 'a\nb\nc\n'))],
2620
builder.build_snapshot(['A-id'], [], revision_id='C-id')
2621
builder.build_snapshot(['A-id'],
2622
[('rename', ('foo', 'bar'))], revision_id='B-id')
2623
builder.build_snapshot(['C-id', 'B-id'],
2624
[('rename', ('foo', 'bar'))], revision_id='E-id') # merge the rename
2625
builder.build_snapshot(['E-id'],
2626
[('rename', ('bar', 'foo'))], revision_id='F-id') # Rename back to BASE
2627
builder.build_snapshot(['B-id', 'C-id'], [], revision_id='D-id')
2449
2628
wt, conflicts = self.do_merge(builder, 'F-id')
2450
2629
self.assertEqual(0, conflicts)
2451
2630
self.assertEqual('foo', wt.id2path('foo-id'))
2453
2632
def test_other_reverted_content_to_base(self):
2454
2633
builder = self.get_builder()
2455
builder.build_snapshot('A-id', None,
2634
builder.build_snapshot(None,
2456
2635
[('add', (u'', 'a-root-id', 'directory', None)),
2457
('add', (u'foo', 'foo-id', 'file', 'base content\n'))])
2458
builder.build_snapshot('C-id', ['A-id'], [])
2459
builder.build_snapshot('B-id', ['A-id'],
2460
[('modify', ('foo-id', 'B content\n'))])
2461
builder.build_snapshot('E-id', ['C-id', 'B-id'],
2462
[('modify', ('foo-id', 'B content\n'))]) # merge the content
2463
builder.build_snapshot('F-id', ['E-id'],
2464
[('modify', ('foo-id', 'base content\n'))]) # Revert back to BASE
2465
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
2636
('add', (u'foo', 'foo-id', 'file', 'base content\n'))],
2638
builder.build_snapshot(['A-id'], [], revision_id='C-id')
2639
builder.build_snapshot(['A-id'],
2640
[('modify', ('foo-id', 'B content\n'))],
2642
builder.build_snapshot(['C-id', 'B-id'],
2643
[('modify', ('foo-id', 'B content\n'))],
2644
revision_id='E-id') # merge the content
2645
builder.build_snapshot(['E-id'],
2646
[('modify', ('foo-id', 'base content\n'))],
2647
revision_id='F-id') # Revert back to BASE
2648
builder.build_snapshot(['B-id', 'C-id'], [], revision_id='D-id')
2466
2649
wt, conflicts = self.do_merge(builder, 'F-id')
2467
2650
self.assertEqual(0, conflicts)
2468
2651
# TODO: We need to use the per-file graph to properly select a BASE
2469
2652
# before this will work. Or at least use the LCA trees to find
2470
2653
# the appropriate content base. (which is B, not A).
2471
self.assertEqual('base content\n', wt.get_file_text('foo-id'))
2654
self.assertEqual('base content\n', wt.get_file_text('foo'))
2473
2656
def test_other_modified_content(self):
2474
2657
builder = self.get_builder()
2475
builder.build_snapshot('A-id', None,
2658
builder.build_snapshot(None,
2476
2659
[('add', (u'', 'a-root-id', 'directory', None)),
2477
('add', (u'foo', 'foo-id', 'file', 'base content\n'))])
2478
builder.build_snapshot('C-id', ['A-id'], [])
2479
builder.build_snapshot('B-id', ['A-id'],
2480
[('modify', ('foo-id', 'B content\n'))])
2481
builder.build_snapshot('E-id', ['C-id', 'B-id'],
2482
[('modify', ('foo-id', 'B content\n'))]) # merge the content
2483
builder.build_snapshot('F-id', ['E-id'],
2484
[('modify', ('foo-id', 'F content\n'))]) # Override B content
2485
builder.build_snapshot('D-id', ['B-id', 'C-id'], [])
2660
('add', (u'foo', 'foo-id', 'file', 'base content\n'))],
2662
builder.build_snapshot(['A-id'], [], revision_id='C-id')
2663
builder.build_snapshot(['A-id'],
2664
[('modify', ('foo-id', 'B content\n'))],
2666
builder.build_snapshot(['C-id', 'B-id'],
2667
[('modify', ('foo-id', 'B content\n'))],
2668
revision_id='E-id') # merge the content
2669
builder.build_snapshot(['E-id'],
2670
[('modify', ('foo-id', 'F content\n'))],
2671
revision_id='F-id') # Override B content
2672
builder.build_snapshot(['B-id', 'C-id'], [], revision_id='D-id')
2486
2673
wt, conflicts = self.do_merge(builder, 'F-id')
2487
2674
self.assertEqual(0, conflicts)
2488
self.assertEqual('F content\n', wt.get_file_text('foo-id'))
2675
self.assertEqual('F content\n', wt.get_file_text('foo'))
2490
2677
def test_all_wt(self):
2491
2678
"""Check behavior if all trees are Working Trees."""
2499
2686
# D E E updates content, renames 'b' => 'c'
2500
2687
builder = self.get_builder()
2501
builder.build_snapshot('A-id', None,
2688
builder.build_snapshot(None,
2502
2689
[('add', (u'', 'a-root-id', 'directory', None)),
2503
2690
('add', (u'a', 'a-id', 'file', 'base content\n')),
2504
('add', (u'foo', 'foo-id', 'file', 'base content\n'))])
2505
builder.build_snapshot('B-id', ['A-id'],
2506
[('modify', ('foo-id', 'B content\n'))])
2507
builder.build_snapshot('C-id', ['A-id'],
2508
[('rename', ('a', 'b'))])
2509
builder.build_snapshot('E-id', ['C-id', 'B-id'],
2691
('add', (u'foo', 'foo-id', 'file', 'base content\n'))],
2693
builder.build_snapshot(['A-id'],
2694
[('modify', ('foo-id', 'B content\n'))],
2696
builder.build_snapshot(['A-id'],
2697
[('rename', ('a', 'b'))],
2699
builder.build_snapshot(['C-id', 'B-id'],
2510
2700
[('rename', ('b', 'c')),
2511
('modify', ('foo-id', 'E content\n'))])
2512
builder.build_snapshot('D-id', ['B-id', 'C-id'],
2513
[('rename', ('a', 'b'))]) # merged change
2701
('modify', ('foo-id', 'E content\n'))],
2703
builder.build_snapshot(['B-id', 'C-id'],
2704
[('rename', ('a', 'b'))], revision_id='D-id') # merged change
2514
2705
wt_this = self.get_wt_from_builder(builder)
2515
wt_base = wt_this.bzrdir.sprout('base', 'A-id').open_workingtree()
2706
wt_base = wt_this.controldir.sprout('base', 'A-id').open_workingtree()
2516
2707
wt_base.lock_read()
2517
2708
self.addCleanup(wt_base.unlock)
2518
wt_lca1 = wt_this.bzrdir.sprout('b-tree', 'B-id').open_workingtree()
2709
wt_lca1 = wt_this.controldir.sprout('b-tree', 'B-id').open_workingtree()
2519
2710
wt_lca1.lock_read()
2520
2711
self.addCleanup(wt_lca1.unlock)
2521
wt_lca2 = wt_this.bzrdir.sprout('c-tree', 'C-id').open_workingtree()
2712
wt_lca2 = wt_this.controldir.sprout('c-tree', 'C-id').open_workingtree()
2522
2713
wt_lca2.lock_read()
2523
2714
self.addCleanup(wt_lca2.unlock)
2524
wt_other = wt_this.bzrdir.sprout('other', 'E-id').open_workingtree()
2715
wt_other = wt_this.controldir.sprout('other', 'E-id').open_workingtree()
2525
2716
wt_other.lock_read()
2526
2717
self.addCleanup(wt_other.unlock)
2527
2718
merge_obj = _mod_merge.Merge3Merger(wt_this, wt_this, wt_base,
2917
3104
conflicts = builder.merge()
2918
3105
# The hook should not call the merge_text() method
2919
3106
self.assertEqual([], self.calls)
3109
class TestMergeIntoBase(tests.TestCaseWithTransport):
3111
def setup_simple_branch(self, relpath, shape=None, root_id=None):
3112
"""One commit, containing tree specified by optional shape.
3114
Default is empty tree (just root entry).
3117
root_id = '%s-root-id' % (relpath,)
3118
wt = self.make_branch_and_tree(relpath)
3119
wt.set_root_id(root_id)
3120
if shape is not None:
3121
adjusted_shape = [relpath + '/' + elem for elem in shape]
3122
self.build_tree(adjusted_shape)
3123
ids = ['%s-%s-id' % (relpath, basename(elem.rstrip('/')))
3125
wt.add(shape, ids=ids)
3126
rev_id = 'r1-%s' % (relpath,)
3127
wt.commit("Initial commit of %s" % (relpath,), rev_id=rev_id)
3128
self.assertEqual(root_id, wt.path2id(''))
3131
def setup_two_branches(self, custom_root_ids=True):
3132
"""Setup 2 branches, one will be a library, the other a project."""
3136
root_id = inventory.ROOT_ID
3137
project_wt = self.setup_simple_branch(
3138
'project', ['README', 'dir/', 'dir/file.c'],
3140
lib_wt = self.setup_simple_branch(
3141
'lib1', ['README', 'Makefile', 'foo.c'], root_id)
3143
return project_wt, lib_wt
3145
def do_merge_into(self, location, merge_as):
3146
"""Helper for using MergeIntoMerger.
3148
:param location: location of directory to merge from, either the
3149
location of a branch or of a path inside a branch.
3150
:param merge_as: the path in a tree to add the new directory as.
3151
:returns: the conflicts from 'do_merge'.
3153
operation = cleanup.OperationWithCleanups(self._merge_into)
3154
return operation.run(location, merge_as)
3156
def _merge_into(self, op, location, merge_as):
3157
# Open and lock the various tree and branch objects
3158
wt, subdir_relpath = WorkingTree.open_containing(merge_as)
3159
op.add_cleanup(wt.lock_write().unlock)
3160
branch_to_merge, subdir_to_merge = _mod_branch.Branch.open_containing(
3162
op.add_cleanup(branch_to_merge.lock_read().unlock)
3163
other_tree = branch_to_merge.basis_tree()
3164
op.add_cleanup(other_tree.lock_read().unlock)
3166
merger = _mod_merge.MergeIntoMerger(this_tree=wt, other_tree=other_tree,
3167
other_branch=branch_to_merge, target_subdir=subdir_relpath,
3168
source_subpath=subdir_to_merge)
3169
merger.set_base_revision(_mod_revision.NULL_REVISION, branch_to_merge)
3170
conflicts = merger.do_merge()
3171
merger.set_pending()
3174
def assertTreeEntriesEqual(self, expected_entries, tree):
3175
"""Assert that 'tree' contains the expected inventory entries.
3177
:param expected_entries: sequence of (path, file-id) pairs.
3179
files = [(path, ie.file_id) for path, ie in tree.iter_entries_by_dir()]
3180
self.assertEqual(expected_entries, files)
3183
class TestMergeInto(TestMergeIntoBase):
3185
def test_newdir_with_unique_roots(self):
3186
"""Merge a branch with a unique root into a new directory."""
3187
project_wt, lib_wt = self.setup_two_branches()
3188
self.do_merge_into('lib1', 'project/lib1')
3189
project_wt.lock_read()
3190
self.addCleanup(project_wt.unlock)
3191
# The r1-lib1 revision should be merged into this one
3192
self.assertEqual(['r1-project', 'r1-lib1'], project_wt.get_parent_ids())
3193
self.assertTreeEntriesEqual(
3194
[('', 'project-root-id'),
3195
('README', 'project-README-id'),
3196
('dir', 'project-dir-id'),
3197
('lib1', 'lib1-root-id'),
3198
('dir/file.c', 'project-file.c-id'),
3199
('lib1/Makefile', 'lib1-Makefile-id'),
3200
('lib1/README', 'lib1-README-id'),
3201
('lib1/foo.c', 'lib1-foo.c-id'),
3204
def test_subdir(self):
3205
"""Merge a branch into a subdirectory of an existing directory."""
3206
project_wt, lib_wt = self.setup_two_branches()
3207
self.do_merge_into('lib1', 'project/dir/lib1')
3208
project_wt.lock_read()
3209
self.addCleanup(project_wt.unlock)
3210
# The r1-lib1 revision should be merged into this one
3211
self.assertEqual(['r1-project', 'r1-lib1'], project_wt.get_parent_ids())
3212
self.assertTreeEntriesEqual(
3213
[('', 'project-root-id'),
3214
('README', 'project-README-id'),
3215
('dir', 'project-dir-id'),
3216
('dir/file.c', 'project-file.c-id'),
3217
('dir/lib1', 'lib1-root-id'),
3218
('dir/lib1/Makefile', 'lib1-Makefile-id'),
3219
('dir/lib1/README', 'lib1-README-id'),
3220
('dir/lib1/foo.c', 'lib1-foo.c-id'),
3223
def test_newdir_with_repeat_roots(self):
3224
"""If the file-id of the dir to be merged already exists a new ID will
3225
be allocated to let the merge happen.
3227
project_wt, lib_wt = self.setup_two_branches(custom_root_ids=False)
3228
root_id = project_wt.path2id('')
3229
self.do_merge_into('lib1', 'project/lib1')
3230
project_wt.lock_read()
3231
self.addCleanup(project_wt.unlock)
3232
# The r1-lib1 revision should be merged into this one
3233
self.assertEqual(['r1-project', 'r1-lib1'], project_wt.get_parent_ids())
3234
new_lib1_id = project_wt.path2id('lib1')
3235
self.assertNotEqual(None, new_lib1_id)
3236
self.assertTreeEntriesEqual(
3238
('README', 'project-README-id'),
3239
('dir', 'project-dir-id'),
3240
('lib1', new_lib1_id),
3241
('dir/file.c', 'project-file.c-id'),
3242
('lib1/Makefile', 'lib1-Makefile-id'),
3243
('lib1/README', 'lib1-README-id'),
3244
('lib1/foo.c', 'lib1-foo.c-id'),
3247
def test_name_conflict(self):
3248
"""When the target directory name already exists a conflict is
3249
generated and the original directory is renamed to foo.moved.
3251
dest_wt = self.setup_simple_branch('dest', ['dir/', 'dir/file.txt'])
3252
src_wt = self.setup_simple_branch('src', ['README'])
3253
conflicts = self.do_merge_into('src', 'dest/dir')
3254
self.assertEqual(1, conflicts)
3256
self.addCleanup(dest_wt.unlock)
3257
# The r1-lib1 revision should be merged into this one
3258
self.assertEqual(['r1-dest', 'r1-src'], dest_wt.get_parent_ids())
3259
self.assertTreeEntriesEqual(
3260
[('', 'dest-root-id'),
3261
('dir', 'src-root-id'),
3262
('dir.moved', 'dest-dir-id'),
3263
('dir/README', 'src-README-id'),
3264
('dir.moved/file.txt', 'dest-file.txt-id'),
3267
def test_file_id_conflict(self):
3268
"""A conflict is generated if the merge-into adds a file (or other
3269
inventory entry) with a file-id that already exists in the target tree.
3271
dest_wt = self.setup_simple_branch('dest', ['file.txt'])
3272
# Make a second tree with a file-id that will clash with file.txt in
3274
src_wt = self.make_branch_and_tree('src')
3275
self.build_tree(['src/README'])
3276
src_wt.add(['README'], ids=['dest-file.txt-id'])
3277
src_wt.commit("Rev 1 of src.", rev_id='r1-src')
3278
conflicts = self.do_merge_into('src', 'dest/dir')
3279
# This is an edge case that shouldn't happen to users very often. So
3280
# we don't care really about the exact presentation of the conflict,
3281
# just that there is one.
3282
self.assertEqual(1, conflicts)
3284
def test_only_subdir(self):
3285
"""When the location points to just part of a tree, merge just that
3288
dest_wt = self.setup_simple_branch('dest')
3289
src_wt = self.setup_simple_branch(
3290
'src', ['hello.txt', 'dir/', 'dir/foo.c'])
3291
conflicts = self.do_merge_into('src/dir', 'dest/dir')
3293
self.addCleanup(dest_wt.unlock)
3294
# The r1-lib1 revision should NOT be merged into this one (this is a
3296
self.assertEqual(['r1-dest'], dest_wt.get_parent_ids())
3297
self.assertTreeEntriesEqual(
3298
[('', 'dest-root-id'),
3299
('dir', 'src-dir-id'),
3300
('dir/foo.c', 'src-foo.c-id'),
3303
def test_only_file(self):
3304
"""An edge case: merge just one file, not a whole dir."""
3305
dest_wt = self.setup_simple_branch('dest')
3306
two_file_wt = self.setup_simple_branch(
3307
'two-file', ['file1.txt', 'file2.txt'])
3308
conflicts = self.do_merge_into('two-file/file1.txt', 'dest/file1.txt')
3310
self.addCleanup(dest_wt.unlock)
3311
# The r1-lib1 revision should NOT be merged into this one
3312
self.assertEqual(['r1-dest'], dest_wt.get_parent_ids())
3313
self.assertTreeEntriesEqual(
3314
[('', 'dest-root-id'), ('file1.txt', 'two-file-file1.txt-id')],
3317
def test_no_such_source_path(self):
3318
"""PathNotInTree is raised if the specified path in the source tree
3321
dest_wt = self.setup_simple_branch('dest')
3322
two_file_wt = self.setup_simple_branch('src', ['dir/'])
3323
self.assertRaises(_mod_merge.PathNotInTree, self.do_merge_into,
3324
'src/no-such-dir', 'dest/foo')
3326
self.addCleanup(dest_wt.unlock)
3327
# The dest tree is unmodified.
3328
self.assertEqual(['r1-dest'], dest_wt.get_parent_ids())
3329
self.assertTreeEntriesEqual([('', 'dest-root-id')], dest_wt)
3331
def test_no_such_target_path(self):
3332
"""PathNotInTree is also raised if the specified path in the target
3333
tree does not exist.
3335
dest_wt = self.setup_simple_branch('dest')
3336
two_file_wt = self.setup_simple_branch('src', ['file.txt'])
3337
self.assertRaises(_mod_merge.PathNotInTree, self.do_merge_into,
3338
'src', 'dest/no-such-dir/foo')
3340
self.addCleanup(dest_wt.unlock)
3341
# The dest tree is unmodified.
3342
self.assertEqual(['r1-dest'], dest_wt.get_parent_ids())
3343
self.assertTreeEntriesEqual([('', 'dest-root-id')], dest_wt)
3346
class TestMergeHooks(TestCaseWithTransport):
3349
super(TestMergeHooks, self).setUp()
3350
self.tree_a = self.make_branch_and_tree('tree_a')
3351
self.build_tree_contents([('tree_a/file', 'content_1')])
3352
self.tree_a.add('file', 'file-id')
3353
self.tree_a.commit('added file')
3355
self.tree_b = self.tree_a.controldir.sprout('tree_b').open_workingtree()
3356
self.build_tree_contents([('tree_b/file', 'content_2')])
3357
self.tree_b.commit('modify file')
3359
def test_pre_merge_hook_inject_different_tree(self):
3360
tree_c = self.tree_b.controldir.sprout('tree_c').open_workingtree()
3361
self.build_tree_contents([('tree_c/file', 'content_3')])
3362
tree_c.commit("more content")
3364
def factory(merger):
3365
self.assertIsInstance(merger, _mod_merge.Merge3Merger)
3366
merger.other_tree = tree_c
3367
calls.append(merger)
3368
_mod_merge.Merger.hooks.install_named_hook('pre_merge',
3369
factory, 'test factory')
3370
self.tree_a.merge_from_branch(self.tree_b.branch)
3372
self.assertFileEqual("content_3", 'tree_a/file')
3373
self.assertLength(1, calls)
3375
def test_post_merge_hook_called(self):
3377
def factory(merger):
3378
self.assertIsInstance(merger, _mod_merge.Merge3Merger)
3379
calls.append(merger)
3380
_mod_merge.Merger.hooks.install_named_hook('post_merge',
3381
factory, 'test factory')
3383
self.tree_a.merge_from_branch(self.tree_b.branch)
3385
self.assertFileEqual("content_2", 'tree_a/file')
3386
self.assertLength(1, calls)