~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Martin Packman
  • Date: 2011-11-23 18:59:43 UTC
  • mto: This revision was merged to the branch mainline in revision 6304.
  • Revision ID: martin.packman@canonical.com-20111123185943-1s2ltxqt5ugohh0w
Add full stops to various registry help strings

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
from __future__ import absolute_import
18
 
 
19
17
import warnings
20
18
 
21
19
from bzrlib.lazy_import import lazy_import
46
44
    decorators,
47
45
    errors,
48
46
    hooks,
49
 
    registry,
50
47
    )
51
48
from bzrlib.symbol_versioning import (
52
49
    deprecated_in,
78
75
            "See the AbstractPerFileMerger API docs for details on how it is "
79
76
            "used by merge.",
80
77
            (2, 1))
81
 
        self.add_hook('pre_merge',
82
 
            'Called before a merge. '
83
 
            'Receives a Merger object as the single argument.',
84
 
            (2, 5))
85
 
        self.add_hook('post_merge',
86
 
            'Called after a merge. '
87
 
            'Receives a Merger object as the single argument. '
88
 
            'The return value is ignored.',
89
 
            (2, 5))
90
78
 
91
79
 
92
80
class AbstractPerFileMerger(object):
104
92
    def merge_contents(self, merge_params):
105
93
        """Attempt to merge the contents of a single file.
106
94
        
107
 
        :param merge_params: A bzrlib.merge.MergeFileHookParams
 
95
        :param merge_params: A bzrlib.merge.MergeHookParams
108
96
        :return: A tuple of (status, chunks), where status is one of
109
97
            'not_applicable', 'success', 'conflicted', or 'delete'.  If status
110
98
            is 'success' or 'conflicted', then chunks should be an iterable of
131
119
 
132
120
    def get_filename(self, params, tree):
133
121
        """Lookup the filename (i.e. basename, not path), given a Tree (e.g.
134
 
        self.merger.this_tree) and a MergeFileHookParams.
 
122
        self.merger.this_tree) and a MergeHookParams.
135
123
        """
136
124
        return osutils.basename(tree.id2path(params.file_id))
137
125
 
138
126
    def get_filepath(self, params, tree):
139
127
        """Calculate the path to the file in a tree.
140
128
 
141
 
        :param params: A MergeFileHookParams describing the file to merge
 
129
        :param params: A MergeHookParams describing the file to merge
142
130
        :param tree: a Tree, e.g. self.merger.this_tree.
143
131
        """
144
132
        return tree.id2path(params.file_id)
151
139
            params.winner == 'other' or
152
140
            # THIS and OTHER aren't both files.
153
141
            not params.is_file_merge() or
154
 
            # The filename doesn't match
 
142
            # The filename doesn't match *.xml
155
143
            not self.file_matches(params)):
156
144
            return 'not_applicable', None
157
145
        return self.merge_matching(params)
233
221
        raise NotImplementedError(self.merge_text)
234
222
 
235
223
 
236
 
class MergeFileHookParams(object):
 
224
class MergeHookParams(object):
237
225
    """Object holding parameters passed to merge_file_content hooks.
238
226
 
239
227
    There are some fields hooks can access:
454
442
        revision_id = _mod_revision.ensure_null(revision_id)
455
443
        return branch, self.revision_tree(revision_id, branch)
456
444
 
 
445
    @deprecated_method(deprecated_in((2, 1, 0)))
 
446
    def ensure_revision_trees(self):
 
447
        if self.this_revision_tree is None:
 
448
            self.this_basis_tree = self.revision_tree(self.this_basis)
 
449
            if self.this_basis == self.this_rev_id:
 
450
                self.this_revision_tree = self.this_basis_tree
 
451
 
 
452
        if self.other_rev_id is None:
 
453
            other_basis_tree = self.revision_tree(self.other_basis)
 
454
            if other_basis_tree.has_changes(self.other_tree):
 
455
                raise errors.WorkingTreeNotRevision(self.this_tree)
 
456
            other_rev_id = self.other_basis
 
457
            self.other_tree = other_basis_tree
 
458
 
 
459
    @deprecated_method(deprecated_in((2, 1, 0)))
 
460
    def file_revisions(self, file_id):
 
461
        self.ensure_revision_trees()
 
462
        if self.this_rev_id is None:
 
463
            if self.this_basis_tree.get_file_sha1(file_id) != \
 
464
                self.this_tree.get_file_sha1(file_id):
 
465
                raise errors.WorkingTreeNotRevision(self.this_tree)
 
466
 
 
467
        trees = (self.this_basis_tree, self.other_tree)
 
468
        return [tree.get_file_revision(file_id) for tree in trees]
 
469
 
 
470
    @deprecated_method(deprecated_in((2, 1, 0)))
 
471
    def check_basis(self, check_clean, require_commits=True):
 
472
        if self.this_basis is None and require_commits is True:
 
473
            raise errors.BzrCommandError(
 
474
                "This branch has no commits."
 
475
                " (perhaps you would prefer 'bzr pull')")
 
476
        if check_clean:
 
477
            self.compare_basis()
 
478
            if self.this_basis != self.this_rev_id:
 
479
                raise errors.UncommittedChanges(self.this_tree)
 
480
 
 
481
    @deprecated_method(deprecated_in((2, 1, 0)))
 
482
    def compare_basis(self):
 
483
        try:
 
484
            basis_tree = self.revision_tree(self.this_tree.last_revision())
 
485
        except errors.NoSuchRevision:
 
486
            basis_tree = self.this_tree.basis_tree()
 
487
        if not self.this_tree.has_changes(basis_tree):
 
488
            self.this_rev_id = self.this_basis
 
489
 
457
490
    def set_interesting_files(self, file_list):
458
491
        self.interesting_files = file_list
459
492
 
504
537
                raise errors.NoCommits(self.other_branch)
505
538
        if self.other_rev_id is not None:
506
539
            self._cached_trees[self.other_rev_id] = self.other_tree
507
 
        self._maybe_fetch(self.other_branch, self.this_branch, self.other_basis)
 
540
        self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
508
541
 
509
542
    def set_other_revision(self, revision_id, other_branch):
510
543
        """Set 'other' based on a branch and revision id
612
645
            self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
613
646
 
614
647
    def make_merger(self):
615
 
        kwargs = {'working_tree': self.this_tree, 'this_tree': self.this_tree,
 
648
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
616
649
                  'other_tree': self.other_tree,
617
650
                  'interesting_ids': self.interesting_ids,
618
651
                  'interesting_files': self.interesting_files,
619
652
                  'this_branch': self.this_branch,
620
 
                  'other_branch': self.other_branch,
621
653
                  'do_merge': False}
622
654
        if self.merge_type.requires_base:
623
655
            kwargs['base_tree'] = self.base_tree
649
681
        merge = self.make_merger()
650
682
        if self.other_branch is not None:
651
683
            self.other_branch.update_references(self.this_branch)
652
 
        for hook in Merger.hooks['pre_merge']:
653
 
            hook(merge)
654
684
        merge.do_merge()
655
 
        for hook in Merger.hooks['post_merge']:
656
 
            hook(merge)
657
685
        if self.recurse == 'down':
658
686
            for relpath, file_id in self.this_tree.iter_references():
659
687
                sub_tree = self.this_tree.get_nested_tree(file_id, relpath)
726
754
                 interesting_ids=None, reprocess=False, show_base=False,
727
755
                 pb=None, pp=None, change_reporter=None,
728
756
                 interesting_files=None, do_merge=True,
729
 
                 cherrypick=False, lca_trees=None, this_branch=None,
730
 
                 other_branch=None):
 
757
                 cherrypick=False, lca_trees=None, this_branch=None):
731
758
        """Initialize the merger object and perform the merge.
732
759
 
733
760
        :param working_tree: The working tree to apply the merge to
736
763
        :param other_tree: The other tree to merge changes from
737
764
        :param this_branch: The branch associated with this_tree.  Defaults to
738
765
            this_tree.branch if not supplied.
739
 
        :param other_branch: The branch associated with other_tree, if any.
740
766
        :param interesting_ids: The file_ids of files that should be
741
767
            participate in the merge.  May not be combined with
742
768
            interesting_files.
764
790
            this_branch = this_tree.branch
765
791
        self.interesting_ids = interesting_ids
766
792
        self.interesting_files = interesting_files
767
 
        self.working_tree = working_tree
768
 
        self.this_tree = this_tree
 
793
        self.this_tree = working_tree
769
794
        self.base_tree = base_tree
770
795
        self.other_tree = other_tree
771
796
        self.this_branch = this_branch
772
 
        self.other_branch = other_branch
773
797
        self._raw_conflicts = []
774
798
        self.cooked_conflicts = []
775
799
        self.reprocess = reprocess
791
815
 
792
816
    def do_merge(self):
793
817
        operation = cleanup.OperationWithCleanups(self._do_merge)
794
 
        self.working_tree.lock_tree_write()
795
 
        operation.add_cleanup(self.working_tree.unlock)
796
 
        self.this_tree.lock_read()
 
818
        self.this_tree.lock_tree_write()
797
819
        operation.add_cleanup(self.this_tree.unlock)
798
820
        self.base_tree.lock_read()
799
821
        operation.add_cleanup(self.base_tree.unlock)
802
824
        operation.run()
803
825
 
804
826
    def _do_merge(self, operation):
805
 
        self.tt = transform.TreeTransform(self.working_tree, None)
 
827
        self.tt = transform.TreeTransform(self.this_tree, None)
806
828
        operation.add_cleanup(self.tt.finalize)
807
829
        self._compute_transform()
808
830
        results = self.tt.apply(no_conflicts=True)
809
831
        self.write_modified(results)
810
832
        try:
811
 
            self.working_tree.add_conflicts(self.cooked_conflicts)
 
833
            self.this_tree.add_conflicts(self.cooked_conflicts)
812
834
        except errors.UnsupportedOperation:
813
835
            pass
814
836
 
821
843
        return operation.run_simple()
822
844
 
823
845
    def _make_preview_transform(self):
824
 
        self.tt = transform.TransformPreview(self.working_tree)
 
846
        self.tt = transform.TransformPreview(self.this_tree)
825
847
        self._compute_transform()
826
848
        return self.tt
827
849
 
832
854
        else:
833
855
            entries = self._entries_lca()
834
856
            resolver = self._lca_multi_way
835
 
        # Prepare merge hooks
836
 
        factories = Merger.hooks['merge_file_content']
837
 
        # One hook for each registered one plus our default merger
838
 
        hooks = [factory(self) for factory in factories] + [self]
839
 
        self.active_hooks = [hook for hook in hooks if hook is not None]
840
857
        child_pb = ui.ui_factory.nested_progress_bar()
841
858
        try:
 
859
            factories = Merger.hooks['merge_file_content']
 
860
            hooks = [factory(self) for factory in factories] + [self]
 
861
            self.active_hooks = [hook for hook in hooks if hook is not None]
842
862
            for num, (file_id, changed, parents3, names3,
843
863
                      executable3) in enumerate(entries):
844
 
                # Try merging each entry
845
 
                child_pb.update(gettext('Preparing file merge'),
846
 
                                num, len(entries))
 
864
                child_pb.update(gettext('Preparing file merge'), num, len(entries))
847
865
                self._merge_names(file_id, parents3, names3, resolver=resolver)
848
866
                if changed:
849
867
                    file_status = self._do_merge_contents(file_id)
940
958
        result = []
941
959
        walker = _mod_tree.MultiWalker(self.other_tree, self._lca_trees)
942
960
 
943
 
        base_inventory = self.base_tree.root_inventory
944
 
        this_inventory = self.this_tree.root_inventory
 
961
        base_inventory = self.base_tree.inventory
 
962
        this_inventory = self.this_tree.inventory
945
963
        for path, file_id, other_ie, lca_values in walker.iter_all():
946
964
            # Is this modified at all from any of the other trees?
947
965
            if other_ie is None:
1091
1109
        other_root = self.tt.trans_id_file_id(other_root_file_id)
1092
1110
        if other_root == self.tt.root:
1093
1111
            return
1094
 
        if self.this_tree.has_id(
1095
 
            self.other_tree.get_root_id()):
 
1112
        if self.this_tree.inventory.has_id(
 
1113
            self.other_tree.inventory.root.file_id):
1096
1114
            # the other tree's root is a non-root in the current tree (as
1097
1115
            # when a previously unrelated branch is merged into another)
1098
1116
            return
1102
1120
            # other_root doesn't have a physical representation. We still need
1103
1121
            # to move any references to the actual root of the tree.
1104
1122
            other_root_is_present = False
1105
 
        # the other tree root is not present in this tree. We are
 
1123
        # 'other_tree.inventory.root' is not present in this tree. We are
1106
1124
        # calling adjust_path for children which *want* to be present with a
1107
1125
        # correct place to go.
1108
 
        for child_id in self.other_tree.iter_children(
1109
 
                self.other_tree.get_root_id()):
1110
 
            trans_id = self.tt.trans_id_file_id(child_id)
 
1126
        for _, child in self.other_tree.inventory.root.children.iteritems():
 
1127
            trans_id = self.tt.trans_id_file_id(child.file_id)
1111
1128
            if not other_root_is_present:
1112
1129
                if self.tt.final_kind(trans_id) is not None:
1113
1130
                    # The item exist in the final tree and has a defined place
1127
1144
    def write_modified(self, results):
1128
1145
        modified_hashes = {}
1129
1146
        for path in results.modified_paths:
1130
 
            file_id = self.working_tree.path2id(self.working_tree.relpath(path))
 
1147
            file_id = self.this_tree.path2id(self.this_tree.relpath(path))
1131
1148
            if file_id is None:
1132
1149
                continue
1133
 
            hash = self.working_tree.get_file_sha1(file_id)
 
1150
            hash = self.this_tree.get_file_sha1(file_id)
1134
1151
            if hash is None:
1135
1152
                continue
1136
1153
            modified_hashes[file_id] = hash
1137
 
        self.working_tree.set_merge_modified(modified_hashes)
 
1154
        self.this_tree.set_merge_modified(modified_hashes)
1138
1155
 
1139
1156
    @staticmethod
1140
1157
    def parent(entry, file_id):
1261
1278
 
1262
1279
    def merge_names(self, file_id):
1263
1280
        def get_entry(tree):
1264
 
            try:
1265
 
                return tree.root_inventory[file_id]
1266
 
            except errors.NoSuchId:
 
1281
            if tree.has_id(file_id):
 
1282
                return tree.inventory[file_id]
 
1283
            else:
1267
1284
                return None
1268
1285
        this_entry = get_entry(self.this_tree)
1269
1286
        other_entry = get_entry(self.other_tree)
1364
1381
        # We have a hypothetical conflict, but if we have files, then we
1365
1382
        # can try to merge the content
1366
1383
        trans_id = self.tt.trans_id_file_id(file_id)
1367
 
        params = MergeFileHookParams(self, file_id, trans_id, this_pair[0],
 
1384
        params = MergeHookParams(self, file_id, trans_id, this_pair[0],
1368
1385
            other_pair[0], winner)
1369
1386
        hooks = self.active_hooks
1370
1387
        hook_status = 'not_applicable'
1941
1958
 
1942
1959
    def _entries_to_incorporate(self):
1943
1960
        """Yields pairs of (inventory_entry, new_parent)."""
1944
 
        other_inv = self.other_tree.root_inventory
 
1961
        other_inv = self.other_tree.inventory
1945
1962
        subdir_id = other_inv.path2id(self._source_subpath)
1946
1963
        if subdir_id is None:
1947
1964
            # XXX: The error would be clearer if it gave the URL of the source
1949
1966
            raise PathNotInTree(self._source_subpath, "Source tree")
1950
1967
        subdir = other_inv[subdir_id]
1951
1968
        parent_in_target = osutils.dirname(self._target_subdir)
1952
 
        target_id = self.this_tree.path2id(parent_in_target)
 
1969
        target_id = self.this_tree.inventory.path2id(parent_in_target)
1953
1970
        if target_id is None:
1954
1971
            raise PathNotInTree(self._target_subdir, "Target tree")
1955
1972
        name_in_target = osutils.basename(self._target_subdir)
1956
1973
        merge_into_root = subdir.copy()
1957
1974
        merge_into_root.name = name_in_target
1958
 
        if self.this_tree.has_id(merge_into_root.file_id):
 
1975
        if self.this_tree.inventory.has_id(merge_into_root.file_id):
1959
1976
            # Give the root a new file-id.
1960
1977
            # This can happen fairly easily if the directory we are
1961
1978
            # incorporating is the root, and both trees have 'TREE_ROOT' as
2021
2038
    merger.set_base_revision(get_revision_id(), this_branch)
2022
2039
    return merger.do_merge()
2023
2040
 
2024
 
 
2025
 
merge_type_registry = registry.Registry()
2026
 
merge_type_registry.register('diff3', Diff3Merger,
2027
 
                             "Merge using external diff3.")
2028
 
merge_type_registry.register('lca', LCAMerger,
2029
 
                             "LCA-newness merge.")
2030
 
merge_type_registry.register('merge3', Merge3Merger,
2031
 
                             "Native diff3-style merge.")
2032
 
merge_type_registry.register('weave', WeaveMerger,
2033
 
                             "Weave-based merge.")
2034
 
 
2035
 
 
2036
2041
def get_merge_type_registry():
2037
 
    """Merge type registry was previously in bzrlib.option
 
2042
    """Merge type registry is in bzrlib.option to avoid circular imports.
2038
2043
 
2039
 
    This method provides a backwards compatible way to retrieve it.
 
2044
    This method provides a sanctioned way to retrieve it.
2040
2045
    """
2041
 
    return merge_type_registry
 
2046
    from bzrlib import option
 
2047
    return option._merge_type_registry
2042
2048
 
2043
2049
 
2044
2050
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):