~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_merge.py

  • Committer: Andrew Bennetts
  • Date: 2010-01-12 03:53:21 UTC
  • mfrom: (4948 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4964.
  • Revision ID: andrew.bennetts@canonical.com-20100112035321-hofpz5p10224ryj3
Merge lp:bzr, resolving conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
import os
18
18
from StringIO import StringIO
29
29
    transform,
30
30
    versionedfile,
31
31
    )
32
 
from bzrlib.branch import Branch
33
32
from bzrlib.conflicts import ConflictList, TextConflict
34
 
from bzrlib.errors import UnrelatedBranches, NoCommits, BzrCommandError
 
33
from bzrlib.errors import UnrelatedBranches, NoCommits
35
34
from bzrlib.merge import transform_tree, merge_inner, _PlanMerge
36
35
from bzrlib.osutils import pathjoin, file_kind
37
36
from bzrlib.tests import TestCaseWithTransport, TestCaseWithMemoryTransport
38
 
from bzrlib.trace import (enable_test_log, disable_test_log)
39
37
from bzrlib.workingtree import WorkingTree
40
38
 
41
39
 
89
87
        self.failIfExists('bar')
90
88
        wt2 = WorkingTree.open('.') # opens branch2
91
89
        self.assertEqual([tip], wt2.get_parent_ids())
92
 
        
 
90
 
93
91
    def test_pending_with_null(self):
94
92
        """When base is forced to revno 0, parent_ids are set"""
95
93
        wt2 = self.test_unrelated()
96
94
        wt1 = WorkingTree.open('.')
97
95
        br1 = wt1.branch
98
96
        br1.fetch(wt2.branch)
99
 
        # merge all of branch 2 into branch 1 even though they 
 
97
        # merge all of branch 2 into branch 1 even though they
100
98
        # are not related.
101
99
        wt1.merge_from_branch(wt2.branch, wt2.last_revision(), 'null:')
102
100
        self.assertEqual([br1.last_revision(), wt2.branch.last_revision()],
150
148
        self.addCleanup(tree_b.unlock)
151
149
        tree_a.commit(message="hello again")
152
150
        log = StringIO()
153
 
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(), 
 
151
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(),
154
152
                    this_tree=tree_b, ignore_zero=True)
155
 
        log = self._get_log(keep_log_file=True)
156
 
        self.failUnless('All changes applied successfully.\n' not in log)
 
153
        self.failUnless('All changes applied successfully.\n' not in
 
154
            self.get_log())
157
155
        tree_b.revert()
158
 
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(), 
 
156
        merge_inner(tree_b.branch, tree_a, tree_b.basis_tree(),
159
157
                    this_tree=tree_b, ignore_zero=False)
160
 
        log = self._get_log(keep_log_file=True)
161
 
        self.failUnless('All changes applied successfully.\n' in log)
 
158
        self.failUnless('All changes applied successfully.\n' in self.get_log())
162
159
 
163
160
    def test_merge_inner_conflicts(self):
164
161
        tree_a = self.make_branch_and_tree('a')
219
216
        tree_a.add('file')
220
217
        tree_a.commit('commit base')
221
218
        # basis_tree() is only guaranteed to be valid as long as it is actually
222
 
        # the basis tree. This mutates the tree after grabbing basis, so go to
223
 
        # the repository.
 
219
        # the basis tree. This test commits to the tree after grabbing basis,
 
220
        # so we go to the repository.
224
221
        base_tree = tree_a.branch.repository.revision_tree(tree_a.last_revision())
225
222
        tree_b = tree_a.bzrdir.sprout('tree_b').open_workingtree()
226
223
        self.build_tree_contents([('tree_a/file', 'content_2')])
227
224
        tree_a.commit('commit other')
228
225
        other_tree = tree_a.basis_tree()
 
226
        # 'file' is now missing but isn't altered in any commit in b so no
 
227
        # change should be applied.
229
228
        os.unlink('tree_b/file')
230
229
        merge_inner(tree_b.branch, other_tree, base_tree, this_tree=tree_b)
231
230
 
248
247
        self.assertEqual(tree_b.conflicts(),
249
248
                         [conflicts.ContentsConflict('file',
250
249
                          file_id='file-id')])
251
 
    
 
250
 
252
251
    def test_merge_type_registry(self):
253
252
        merge_type_option = option.Option.OPTIONS['merge-type']
254
 
        self.assertFalse('merge4' in [x[0] for x in 
 
253
        self.assertFalse('merge4' in [x[0] for x in
255
254
                        merge_type_option.iter_switches()])
256
255
        registry = _mod_merge.get_merge_type_registry()
257
256
        registry.register_lazy('merge4', 'bzrlib.merge', 'Merge4Merger',
258
257
                               'time-travelling merge')
259
 
        self.assertTrue('merge4' in [x[0] for x in 
 
258
        self.assertTrue('merge4' in [x[0] for x in
260
259
                        merge_type_option.iter_switches()])
261
260
        registry.remove('merge4')
262
 
        self.assertFalse('merge4' in [x[0] for x in 
 
261
        self.assertFalse('merge4' in [x[0] for x in
263
262
                        merge_type_option.iter_switches()])
264
263
 
265
264
    def test_merge_other_moves_we_deleted(self):
313
312
        merger.do_merge()
314
313
        self.assertEqual(tree_a.get_parent_ids(), [tree_b.last_revision()])
315
314
 
 
315
    def test_Merger_defaults_to_DummyProgress(self):
 
316
        branch = self.make_branch('branch')
 
317
        merger = _mod_merge.Merger(branch, pb=None)
 
318
        self.assertIsInstance(merger._pb, progress.DummyProgress)
 
319
 
316
320
    def prepare_cherrypick(self):
317
321
        """Prepare a pair of trees for cherrypicking tests.
318
322
 
518
522
        self.add_uncommitted_version(('root', 'C:'), [('root', 'A')], 'fabg')
519
523
        return _PlanMerge('B:', 'C:', self.plan_merge_vf, ('root',))
520
524
 
 
525
    def test_base_from_plan(self):
 
526
        self.setup_plan_merge()
 
527
        plan = self.plan_merge_vf.plan_merge('B', 'C')
 
528
        pwm = versionedfile.PlanWeaveMerge(plan)
 
529
        self.assertEqual(['a\n', 'b\n', 'c\n'], pwm.base_from_plan())
 
530
 
521
531
    def test_unique_lines(self):
522
532
        plan = self.setup_plan_merge()
523
533
        self.assertEqual(plan._unique_lines(
718
728
 
719
729
    def test_plan_merge_insert_order(self):
720
730
        """Weave merges are sensitive to the order of insertion.
721
 
        
 
731
 
722
732
        Specifically for overlapping regions, it effects which region gets put
723
733
        'first'. And when a user resolves an overlapping merge, if they use the
724
734
        same ordering, then the lines match the parents, if they don't only
821
831
                          ('unchanged', 'f\n'),
822
832
                          ('unchanged', 'g\n')],
823
833
                         list(plan))
 
834
        plan = self.plan_merge_vf.plan_lca_merge('F', 'G')
 
835
        # This is one of the main differences between plan_merge and
 
836
        # plan_lca_merge. plan_lca_merge generates a conflict for 'x => z',
 
837
        # because 'x' was not present in one of the bases. However, in this
 
838
        # case it is spurious because 'x' does not exist in the global base A.
 
839
        self.assertEqual([
 
840
                          ('unchanged', 'h\n'),
 
841
                          ('unchanged', 'a\n'),
 
842
                          ('conflicted-a', 'x\n'),
 
843
                          ('new-b', 'z\n'),
 
844
                          ('unchanged', 'c\n'),
 
845
                          ('unchanged', 'd\n'),
 
846
                          ('unchanged', 'y\n'),
 
847
                          ('unchanged', 'f\n'),
 
848
                          ('unchanged', 'g\n')],
 
849
                         list(plan))
 
850
 
 
851
    def test_criss_cross_flip_flop(self):
 
852
        # This is specificly trying to trigger problems when using limited
 
853
        # ancestry and weaves. The ancestry graph looks like:
 
854
        #       XX      unused ancestor, should not show up in the weave
 
855
        #       |
 
856
        #       A       Unique LCA
 
857
        #      / \  
 
858
        #     B   C     B & C both introduce a new line
 
859
        #     |\ /|  
 
860
        #     | X |  
 
861
        #     |/ \| 
 
862
        #     D   E     B & C are both merged, so both are common ancestors
 
863
        #               In the process of merging, both sides order the new
 
864
        #               lines differently
 
865
        #
 
866
        self.add_rev('root', 'XX', [], 'qrs')
 
867
        self.add_rev('root', 'A', ['XX'], 'abcdef')
 
868
        self.add_rev('root', 'B', ['A'], 'abcdgef')
 
869
        self.add_rev('root', 'C', ['A'], 'abcdhef')
 
870
        self.add_rev('root', 'D', ['B', 'C'], 'abcdghef')
 
871
        self.add_rev('root', 'E', ['C', 'B'], 'abcdhgef')
 
872
        plan = list(self.plan_merge_vf.plan_merge('D', 'E'))
 
873
        self.assertEqual([
 
874
                          ('unchanged', 'a\n'),
 
875
                          ('unchanged', 'b\n'),
 
876
                          ('unchanged', 'c\n'),
 
877
                          ('unchanged', 'd\n'),
 
878
                          ('new-b', 'h\n'),
 
879
                          ('unchanged', 'g\n'),
 
880
                          ('killed-b', 'h\n'),
 
881
                          ('unchanged', 'e\n'),
 
882
                          ('unchanged', 'f\n'),
 
883
                         ], plan)
 
884
        pwm = versionedfile.PlanWeaveMerge(plan)
 
885
        self.assertEqualDiff('\n'.join('abcdghef') + '\n',
 
886
                             ''.join(pwm.base_from_plan()))
 
887
        # Reversing the order reverses the merge plan, and final order of 'hg'
 
888
        # => 'gh'
 
889
        plan = list(self.plan_merge_vf.plan_merge('E', 'D'))
 
890
        self.assertEqual([
 
891
                          ('unchanged', 'a\n'),
 
892
                          ('unchanged', 'b\n'),
 
893
                          ('unchanged', 'c\n'),
 
894
                          ('unchanged', 'd\n'),
 
895
                          ('new-b', 'g\n'),
 
896
                          ('unchanged', 'h\n'),
 
897
                          ('killed-b', 'g\n'),
 
898
                          ('unchanged', 'e\n'),
 
899
                          ('unchanged', 'f\n'),
 
900
                         ], plan)
 
901
        pwm = versionedfile.PlanWeaveMerge(plan)
 
902
        self.assertEqualDiff('\n'.join('abcdhgef') + '\n',
 
903
                             ''.join(pwm.base_from_plan()))
 
904
        # This is where lca differs, in that it (fairly correctly) determines
 
905
        # that there is a conflict because both sides resolved the merge
 
906
        # differently
 
907
        plan = list(self.plan_merge_vf.plan_lca_merge('D', 'E'))
 
908
        self.assertEqual([
 
909
                          ('unchanged', 'a\n'),
 
910
                          ('unchanged', 'b\n'),
 
911
                          ('unchanged', 'c\n'),
 
912
                          ('unchanged', 'd\n'),
 
913
                          ('conflicted-b', 'h\n'),
 
914
                          ('unchanged', 'g\n'),
 
915
                          ('conflicted-a', 'h\n'),
 
916
                          ('unchanged', 'e\n'),
 
917
                          ('unchanged', 'f\n'),
 
918
                         ], plan)
 
919
        pwm = versionedfile.PlanWeaveMerge(plan)
 
920
        self.assertEqualDiff('\n'.join('abcdgef') + '\n',
 
921
                             ''.join(pwm.base_from_plan()))
 
922
        # Reversing it changes what line is doubled, but still gives a
 
923
        # double-conflict
 
924
        plan = list(self.plan_merge_vf.plan_lca_merge('E', 'D'))
 
925
        self.assertEqual([
 
926
                          ('unchanged', 'a\n'),
 
927
                          ('unchanged', 'b\n'),
 
928
                          ('unchanged', 'c\n'),
 
929
                          ('unchanged', 'd\n'),
 
930
                          ('conflicted-b', 'g\n'),
 
931
                          ('unchanged', 'h\n'),
 
932
                          ('conflicted-a', 'g\n'),
 
933
                          ('unchanged', 'e\n'),
 
934
                          ('unchanged', 'f\n'),
 
935
                         ], plan)
 
936
        pwm = versionedfile.PlanWeaveMerge(plan)
 
937
        self.assertEqualDiff('\n'.join('abcdhef') + '\n',
 
938
                             ''.join(pwm.base_from_plan()))
824
939
 
825
940
    def assertRemoveExternalReferences(self, filtered_parent_map,
826
941
                                       child_map, tails, parent_map):
1026
1141
                         ], list(plan))
1027
1142
 
1028
1143
 
1029
 
class TestMergeImplementation(object):
1030
 
 
1031
 
    def do_merge(self, target_tree, source_tree, **kwargs):
1032
 
        merger = _mod_merge.Merger.from_revision_ids(progress.DummyProgress(),
1033
 
            target_tree, source_tree.last_revision(),
1034
 
            other_branch=source_tree.branch)
1035
 
        merger.merge_type=self.merge_type
1036
 
        for name, value in kwargs.items():
1037
 
            setattr(merger, name, value)
1038
 
        merger.do_merge()
1039
 
 
1040
 
    def test_merge_specific_file(self):
1041
 
        this_tree = self.make_branch_and_tree('this')
1042
 
        this_tree.lock_write()
1043
 
        self.addCleanup(this_tree.unlock)
1044
 
        self.build_tree_contents([
1045
 
            ('this/file1', 'a\nb\n'),
1046
 
            ('this/file2', 'a\nb\n')
1047
 
        ])
1048
 
        this_tree.add(['file1', 'file2'])
1049
 
        this_tree.commit('Added files')
1050
 
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
1051
 
        self.build_tree_contents([
1052
 
            ('other/file1', 'a\nb\nc\n'),
1053
 
            ('other/file2', 'a\nb\nc\n')
1054
 
        ])
1055
 
        other_tree.commit('modified both')
1056
 
        self.build_tree_contents([
1057
 
            ('this/file1', 'd\na\nb\n'),
1058
 
            ('this/file2', 'd\na\nb\n')
1059
 
        ])
1060
 
        this_tree.commit('modified both')
1061
 
        self.do_merge(this_tree, other_tree, interesting_files=['file1'])
1062
 
        self.assertFileEqual('d\na\nb\nc\n', 'this/file1')
1063
 
        self.assertFileEqual('d\na\nb\n', 'this/file2')
1064
 
 
1065
 
    def test_merge_move_and_change(self):
1066
 
        this_tree = self.make_branch_and_tree('this')
1067
 
        this_tree.lock_write()
1068
 
        self.addCleanup(this_tree.unlock)
1069
 
        self.build_tree_contents([
1070
 
            ('this/file1', 'line 1\nline 2\nline 3\nline 4\n'),
1071
 
        ])
1072
 
        this_tree.add('file1',)
1073
 
        this_tree.commit('Added file')
1074
 
        other_tree = this_tree.bzrdir.sprout('other').open_workingtree()
1075
 
        self.build_tree_contents([
1076
 
            ('other/file1', 'line 1\nline 2 to 2.1\nline 3\nline 4\n'),
1077
 
        ])
1078
 
        other_tree.commit('Changed 2 to 2.1')
1079
 
        self.build_tree_contents([
1080
 
            ('this/file1', 'line 1\nline 3\nline 2\nline 4\n'),
1081
 
        ])
1082
 
        this_tree.commit('Swapped 2 & 3')
1083
 
        self.do_merge(this_tree, other_tree)
1084
 
        self.assertFileEqual('line 1\n'
1085
 
            '<<<<<<< TREE\n'
1086
 
            'line 3\n'
1087
 
            'line 2\n'
1088
 
            '=======\n'
1089
 
            'line 2 to 2.1\n'
1090
 
            'line 3\n'
1091
 
            '>>>>>>> MERGE-SOURCE\n'
1092
 
            'line 4\n', 'this/file1')
1093
 
 
1094
 
 
1095
 
class TestMerge3Merge(TestCaseWithTransport, TestMergeImplementation):
1096
 
 
1097
 
    merge_type = _mod_merge.Merge3Merger
1098
 
 
1099
 
 
1100
 
class TestWeaveMerge(TestCaseWithTransport, TestMergeImplementation):
1101
 
 
1102
 
    merge_type = _mod_merge.WeaveMerger
1103
 
 
1104
 
 
1105
 
class TestLCAMerge(TestCaseWithTransport, TestMergeImplementation):
1106
 
 
1107
 
    merge_type = _mod_merge.LCAMerger
1108
 
 
1109
 
    def test_merge_move_and_change(self):
1110
 
        self.expectFailure("lca merge doesn't conflict for move and change",
1111
 
            super(TestLCAMerge, self).test_merge_move_and_change)
1112
 
 
1113
 
 
1114
1144
class LoggingMerger(object):
1115
1145
    # These seem to be the required attributes
1116
1146
    requires_base = False
1182
1212
 
1183
1213
class TestMergerInMemory(TestMergerBase):
1184
1214
 
 
1215
    def test_cache_trees_with_revision_ids_None(self):
 
1216
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1217
        original_cache = dict(merger._cached_trees)
 
1218
        merger.cache_trees_with_revision_ids([None])
 
1219
        self.assertEqual(original_cache, merger._cached_trees)
 
1220
 
 
1221
    def test_cache_trees_with_revision_ids_no_revision_id(self):
 
1222
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1223
        original_cache = dict(merger._cached_trees)
 
1224
        tree = self.make_branch_and_memory_tree('tree')
 
1225
        merger.cache_trees_with_revision_ids([tree])
 
1226
        self.assertEqual(original_cache, merger._cached_trees)
 
1227
 
 
1228
    def test_cache_trees_with_revision_ids_having_revision_id(self):
 
1229
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
 
1230
        original_cache = dict(merger._cached_trees)
 
1231
        tree = merger.this_branch.repository.revision_tree('B-id')
 
1232
        original_cache['B-id'] = tree
 
1233
        merger.cache_trees_with_revision_ids([tree])
 
1234
        self.assertEqual(original_cache, merger._cached_trees)
 
1235
 
1185
1236
    def test_find_base(self):
1186
1237
        merger = self.make_Merger(self.setup_simple_graph(), 'C-id')
1187
1238
        self.assertEqual('A-id', merger.base_rev_id)