/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

Add bzrlib.pyutils, which has get_named_object, a wrapper around __import__.

This is used to replace various ad hoc implementations of the same logic,
notably the version used in registry's _LazyObjectGetter which had a bug when
getting a module without also getting a member.  And of course, this new
function has unit tests, unlike the replaced code.

This also adds a KnownHooksRegistry subclass to provide a more natural home for
some other logic.

I'm not thrilled about the name of the new module or the new functions, but it's
hard to think of good names for such generic functionality.

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
from StringIO import StringIO
19
19
 
20
20
from bzrlib import (
 
21
    branch as _mod_branch,
 
22
    cleanup,
21
23
    conflicts,
22
24
    errors,
 
25
    inventory,
23
26
    knit,
24
27
    memorytree,
25
28
    merge as _mod_merge,
26
29
    option,
27
 
    progress,
 
30
    revision as _mod_revision,
28
31
    tests,
29
32
    transform,
30
33
    versionedfile,
32
35
from bzrlib.conflicts import ConflictList, TextConflict
33
36
from bzrlib.errors import UnrelatedBranches, NoCommits
34
37
from bzrlib.merge import transform_tree, merge_inner, _PlanMerge
35
 
from bzrlib.osutils import pathjoin, file_kind
 
38
from bzrlib.osutils import basename, pathjoin, file_kind
36
39
from bzrlib.tests import (
37
40
    TestCaseWithMemoryTransport,
38
41
    TestCaseWithTransport,
2917
2920
        conflicts = builder.merge()
2918
2921
        # The hook should not call the merge_text() method
2919
2922
        self.assertEqual([], self.calls)
 
2923
 
 
2924
 
 
2925
class TestMergeIntoBase(tests.TestCaseWithTransport):
 
2926
 
 
2927
    def setup_simple_branch(self, relpath, shape=None, root_id=None):
 
2928
        """One commit, containing tree specified by optional shape.
 
2929
        
 
2930
        Default is empty tree (just root entry).
 
2931
        """
 
2932
        if root_id is None:
 
2933
            root_id = '%s-root-id' % (relpath,)
 
2934
        wt = self.make_branch_and_tree(relpath)
 
2935
        wt.set_root_id(root_id)
 
2936
        if shape is not None:
 
2937
            adjusted_shape = [relpath + '/' + elem for elem in shape]
 
2938
            self.build_tree(adjusted_shape)
 
2939
            ids = ['%s-%s-id' % (relpath, basename(elem.rstrip('/')))
 
2940
                   for elem in shape]
 
2941
            wt.add(shape, ids=ids)
 
2942
        rev_id = 'r1-%s' % (relpath,)
 
2943
        wt.commit("Initial commit of %s" % (relpath,), rev_id=rev_id)
 
2944
        self.assertEqual(root_id, wt.path2id(''))
 
2945
        return wt
 
2946
 
 
2947
    def setup_two_branches(self, custom_root_ids=True):
 
2948
        """Setup 2 branches, one will be a library, the other a project."""
 
2949
        if custom_root_ids:
 
2950
            root_id = None
 
2951
        else:
 
2952
            root_id = inventory.ROOT_ID
 
2953
        project_wt = self.setup_simple_branch(
 
2954
            'project', ['README', 'dir/', 'dir/file.c'],
 
2955
            root_id)
 
2956
        lib_wt = self.setup_simple_branch(
 
2957
            'lib1', ['README', 'Makefile', 'foo.c'], root_id)
 
2958
 
 
2959
        return project_wt, lib_wt
 
2960
 
 
2961
    def do_merge_into(self, location, merge_as):
 
2962
        """Helper for using MergeIntoMerger.
 
2963
        
 
2964
        :param location: location of directory to merge from, either the
 
2965
            location of a branch or of a path inside a branch.
 
2966
        :param merge_as: the path in a tree to add the new directory as.
 
2967
        :returns: the conflicts from 'do_merge'.
 
2968
        """
 
2969
        operation = cleanup.OperationWithCleanups(self._merge_into)
 
2970
        return operation.run(location, merge_as)
 
2971
 
 
2972
    def _merge_into(self, op, location, merge_as):
 
2973
        # Open and lock the various tree and branch objects
 
2974
        wt, subdir_relpath = WorkingTree.open_containing(merge_as)
 
2975
        op.add_cleanup(wt.lock_write().unlock)
 
2976
        branch_to_merge, subdir_to_merge = _mod_branch.Branch.open_containing(
 
2977
            location)
 
2978
        op.add_cleanup(branch_to_merge.lock_read().unlock)
 
2979
        other_tree = branch_to_merge.basis_tree()
 
2980
        op.add_cleanup(other_tree.lock_read().unlock)
 
2981
        # Perform the merge
 
2982
        merger = _mod_merge.MergeIntoMerger(this_tree=wt, other_tree=other_tree,
 
2983
            other_branch=branch_to_merge, target_subdir=subdir_relpath,
 
2984
            source_subpath=subdir_to_merge)
 
2985
        merger.set_base_revision(_mod_revision.NULL_REVISION, branch_to_merge)
 
2986
        conflicts = merger.do_merge()
 
2987
        merger.set_pending()
 
2988
        return conflicts
 
2989
 
 
2990
    def assertTreeEntriesEqual(self, expected_entries, tree):
 
2991
        """Assert that 'tree' contains the expected inventory entries.
 
2992
 
 
2993
        :param expected_entries: sequence of (path, file-id) pairs.
 
2994
        """
 
2995
        files = [(path, ie.file_id) for path, ie in tree.iter_entries_by_dir()]
 
2996
        self.assertEqual(expected_entries, files)
 
2997
 
 
2998
 
 
2999
class TestMergeInto(TestMergeIntoBase):
 
3000
 
 
3001
    def test_newdir_with_unique_roots(self):
 
3002
        """Merge a branch with a unique root into a new directory."""
 
3003
        project_wt, lib_wt = self.setup_two_branches()
 
3004
        self.do_merge_into('lib1', 'project/lib1')
 
3005
        project_wt.lock_read()
 
3006
        self.addCleanup(project_wt.unlock)
 
3007
        # The r1-lib1 revision should be merged into this one
 
3008
        self.assertEqual(['r1-project', 'r1-lib1'], project_wt.get_parent_ids())
 
3009
        self.assertTreeEntriesEqual(
 
3010
            [('', 'project-root-id'),
 
3011
             ('README', 'project-README-id'),
 
3012
             ('dir', 'project-dir-id'),
 
3013
             ('lib1', 'lib1-root-id'),
 
3014
             ('dir/file.c', 'project-file.c-id'),
 
3015
             ('lib1/Makefile', 'lib1-Makefile-id'),
 
3016
             ('lib1/README', 'lib1-README-id'),
 
3017
             ('lib1/foo.c', 'lib1-foo.c-id'),
 
3018
            ], project_wt)
 
3019
 
 
3020
    def test_subdir(self):
 
3021
        """Merge a branch into a subdirectory of an existing directory."""
 
3022
        project_wt, lib_wt = self.setup_two_branches()
 
3023
        self.do_merge_into('lib1', 'project/dir/lib1')
 
3024
        project_wt.lock_read()
 
3025
        self.addCleanup(project_wt.unlock)
 
3026
        # The r1-lib1 revision should be merged into this one
 
3027
        self.assertEqual(['r1-project', 'r1-lib1'], project_wt.get_parent_ids())
 
3028
        self.assertTreeEntriesEqual(
 
3029
            [('', 'project-root-id'),
 
3030
             ('README', 'project-README-id'),
 
3031
             ('dir', 'project-dir-id'),
 
3032
             ('dir/file.c', 'project-file.c-id'),
 
3033
             ('dir/lib1', 'lib1-root-id'),
 
3034
             ('dir/lib1/Makefile', 'lib1-Makefile-id'),
 
3035
             ('dir/lib1/README', 'lib1-README-id'),
 
3036
             ('dir/lib1/foo.c', 'lib1-foo.c-id'),
 
3037
            ], project_wt)
 
3038
 
 
3039
    def test_newdir_with_repeat_roots(self):
 
3040
        """If the file-id of the dir to be merged already exists a new ID will
 
3041
        be allocated to let the merge happen.
 
3042
        """
 
3043
        project_wt, lib_wt = self.setup_two_branches(custom_root_ids=False)
 
3044
        root_id = project_wt.path2id('')
 
3045
        self.do_merge_into('lib1', 'project/lib1')
 
3046
        project_wt.lock_read()
 
3047
        self.addCleanup(project_wt.unlock)
 
3048
        # The r1-lib1 revision should be merged into this one
 
3049
        self.assertEqual(['r1-project', 'r1-lib1'], project_wt.get_parent_ids())
 
3050
        new_lib1_id = project_wt.path2id('lib1')
 
3051
        self.assertNotEqual(None, new_lib1_id)
 
3052
        self.assertTreeEntriesEqual(
 
3053
            [('', root_id),
 
3054
             ('README', 'project-README-id'),
 
3055
             ('dir', 'project-dir-id'),
 
3056
             ('lib1', new_lib1_id),
 
3057
             ('dir/file.c', 'project-file.c-id'),
 
3058
             ('lib1/Makefile', 'lib1-Makefile-id'),
 
3059
             ('lib1/README', 'lib1-README-id'),
 
3060
             ('lib1/foo.c', 'lib1-foo.c-id'),
 
3061
            ], project_wt)
 
3062
 
 
3063
    def test_name_conflict(self):
 
3064
        """When the target directory name already exists a conflict is
 
3065
        generated and the original directory is renamed to foo.moved.
 
3066
        """
 
3067
        dest_wt = self.setup_simple_branch('dest', ['dir/', 'dir/file.txt'])
 
3068
        src_wt = self.setup_simple_branch('src', ['README'])
 
3069
        conflicts = self.do_merge_into('src', 'dest/dir')
 
3070
        self.assertEqual(1, conflicts)
 
3071
        dest_wt.lock_read()
 
3072
        self.addCleanup(dest_wt.unlock)
 
3073
        # The r1-lib1 revision should be merged into this one
 
3074
        self.assertEqual(['r1-dest', 'r1-src'], dest_wt.get_parent_ids())
 
3075
        self.assertTreeEntriesEqual(
 
3076
            [('', 'dest-root-id'),
 
3077
             ('dir', 'src-root-id'),
 
3078
             ('dir.moved', 'dest-dir-id'),
 
3079
             ('dir/README', 'src-README-id'),
 
3080
             ('dir.moved/file.txt', 'dest-file.txt-id'),
 
3081
            ], dest_wt)
 
3082
 
 
3083
    def test_file_id_conflict(self):
 
3084
        """A conflict is generated if the merge-into adds a file (or other
 
3085
        inventory entry) with a file-id that already exists in the target tree.
 
3086
        """
 
3087
        dest_wt = self.setup_simple_branch('dest', ['file.txt'])
 
3088
        # Make a second tree with a file-id that will clash with file.txt in
 
3089
        # dest.
 
3090
        src_wt = self.make_branch_and_tree('src')
 
3091
        self.build_tree(['src/README'])
 
3092
        src_wt.add(['README'], ids=['dest-file.txt-id'])
 
3093
        src_wt.commit("Rev 1 of src.", rev_id='r1-src')
 
3094
        conflicts = self.do_merge_into('src', 'dest/dir')
 
3095
        # This is an edge case that shouldn't happen to users very often.  So
 
3096
        # we don't care really about the exact presentation of the conflict,
 
3097
        # just that there is one.
 
3098
        self.assertEqual(1, conflicts)
 
3099
 
 
3100
    def test_only_subdir(self):
 
3101
        """When the location points to just part of a tree, merge just that
 
3102
        subtree.
 
3103
        """
 
3104
        dest_wt = self.setup_simple_branch('dest')
 
3105
        src_wt = self.setup_simple_branch(
 
3106
            'src', ['hello.txt', 'dir/', 'dir/foo.c'])
 
3107
        conflicts = self.do_merge_into('src/dir', 'dest/dir')
 
3108
        dest_wt.lock_read()
 
3109
        self.addCleanup(dest_wt.unlock)
 
3110
        # The r1-lib1 revision should NOT be merged into this one (this is a
 
3111
        # partial merge).
 
3112
        self.assertEqual(['r1-dest'], dest_wt.get_parent_ids())
 
3113
        self.assertTreeEntriesEqual(
 
3114
            [('', 'dest-root-id'),
 
3115
             ('dir', 'src-dir-id'),
 
3116
             ('dir/foo.c', 'src-foo.c-id'),
 
3117
            ], dest_wt)
 
3118
 
 
3119
    def test_only_file(self):
 
3120
        """An edge case: merge just one file, not a whole dir."""
 
3121
        dest_wt = self.setup_simple_branch('dest')
 
3122
        two_file_wt = self.setup_simple_branch(
 
3123
            'two-file', ['file1.txt', 'file2.txt'])
 
3124
        conflicts = self.do_merge_into('two-file/file1.txt', 'dest/file1.txt')
 
3125
        dest_wt.lock_read()
 
3126
        self.addCleanup(dest_wt.unlock)
 
3127
        # The r1-lib1 revision should NOT be merged into this one
 
3128
        self.assertEqual(['r1-dest'], dest_wt.get_parent_ids())
 
3129
        self.assertTreeEntriesEqual(
 
3130
            [('', 'dest-root-id'), ('file1.txt', 'two-file-file1.txt-id')],
 
3131
            dest_wt)
 
3132
 
 
3133
    def test_no_such_source_path(self):
 
3134
        """PathNotInTree is raised if the specified path in the source tree
 
3135
        does not exist.
 
3136
        """
 
3137
        dest_wt = self.setup_simple_branch('dest')
 
3138
        two_file_wt = self.setup_simple_branch('src', ['dir/'])
 
3139
        self.assertRaises(_mod_merge.PathNotInTree, self.do_merge_into,
 
3140
            'src/no-such-dir', 'dest/foo')
 
3141
        dest_wt.lock_read()
 
3142
        self.addCleanup(dest_wt.unlock)
 
3143
        # The dest tree is unmodified.
 
3144
        self.assertEqual(['r1-dest'], dest_wt.get_parent_ids())
 
3145
        self.assertTreeEntriesEqual([('', 'dest-root-id')], dest_wt)
 
3146
 
 
3147
    def test_no_such_target_path(self):
 
3148
        """PathNotInTree is also raised if the specified path in the target
 
3149
        tree does not exist.
 
3150
        """
 
3151
        dest_wt = self.setup_simple_branch('dest')
 
3152
        two_file_wt = self.setup_simple_branch('src', ['file.txt'])
 
3153
        self.assertRaises(_mod_merge.PathNotInTree, self.do_merge_into,
 
3154
            'src', 'dest/no-such-dir/foo')
 
3155
        dest_wt.lock_read()
 
3156
        self.addCleanup(dest_wt.unlock)
 
3157
        # The dest tree is unmodified.
 
3158
        self.assertEqual(['r1-dest'], dest_wt.get_parent_ids())
 
3159
        self.assertTreeEntriesEqual([('', 'dest-root-id')], dest_wt)