~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Samuel Bronson
  • Date: 2012-08-30 20:36:18 UTC
  • mto: (6015.57.3 2.4)
  • mto: This revision was merged to the branch mainline in revision 6558.
  • Revision ID: naesten@gmail.com-20120830203618-y2dzw91nqpvpgxvx
Update INSTALL for switch to Python 2.6 and up.

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
40
38
    versionedfile,
41
39
    workingtree,
42
40
    )
43
 
from bzrlib.i18n import gettext
44
41
""")
45
42
from bzrlib import (
46
43
    decorators,
47
44
    errors,
48
45
    hooks,
49
 
    registry,
50
46
    )
51
47
from bzrlib.symbol_versioning import (
52
48
    deprecated_in,
78
74
            "See the AbstractPerFileMerger API docs for details on how it is "
79
75
            "used by merge.",
80
76
            (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
77
 
91
78
 
92
79
class AbstractPerFileMerger(object):
104
91
    def merge_contents(self, merge_params):
105
92
        """Attempt to merge the contents of a single file.
106
93
        
107
 
        :param merge_params: A bzrlib.merge.MergeFileHookParams
 
94
        :param merge_params: A bzrlib.merge.MergeHookParams
108
95
        :return: A tuple of (status, chunks), where status is one of
109
96
            'not_applicable', 'success', 'conflicted', or 'delete'.  If status
110
97
            is 'success' or 'conflicted', then chunks should be an iterable of
131
118
 
132
119
    def get_filename(self, params, tree):
133
120
        """Lookup the filename (i.e. basename, not path), given a Tree (e.g.
134
 
        self.merger.this_tree) and a MergeFileHookParams.
 
121
        self.merger.this_tree) and a MergeHookParams.
135
122
        """
136
123
        return osutils.basename(tree.id2path(params.file_id))
137
124
 
138
125
    def get_filepath(self, params, tree):
139
126
        """Calculate the path to the file in a tree.
140
127
 
141
 
        :param params: A MergeFileHookParams describing the file to merge
 
128
        :param params: A MergeHookParams describing the file to merge
142
129
        :param tree: a Tree, e.g. self.merger.this_tree.
143
130
        """
144
131
        return tree.id2path(params.file_id)
151
138
            params.winner == 'other' or
152
139
            # THIS and OTHER aren't both files.
153
140
            not params.is_file_merge() or
154
 
            # The filename doesn't match
 
141
            # The filename doesn't match *.xml
155
142
            not self.file_matches(params)):
156
143
            return 'not_applicable', None
157
144
        return self.merge_matching(params)
233
220
        raise NotImplementedError(self.merge_text)
234
221
 
235
222
 
236
 
class MergeFileHookParams(object):
 
223
class MergeHookParams(object):
237
224
    """Object holding parameters passed to merge_file_content hooks.
238
225
 
239
226
    There are some fields hooks can access:
454
441
        revision_id = _mod_revision.ensure_null(revision_id)
455
442
        return branch, self.revision_tree(revision_id, branch)
456
443
 
 
444
    @deprecated_method(deprecated_in((2, 1, 0)))
 
445
    def ensure_revision_trees(self):
 
446
        if self.this_revision_tree is None:
 
447
            self.this_basis_tree = self.revision_tree(self.this_basis)
 
448
            if self.this_basis == self.this_rev_id:
 
449
                self.this_revision_tree = self.this_basis_tree
 
450
 
 
451
        if self.other_rev_id is None:
 
452
            other_basis_tree = self.revision_tree(self.other_basis)
 
453
            if other_basis_tree.has_changes(self.other_tree):
 
454
                raise errors.WorkingTreeNotRevision(self.this_tree)
 
455
            other_rev_id = self.other_basis
 
456
            self.other_tree = other_basis_tree
 
457
 
 
458
    @deprecated_method(deprecated_in((2, 1, 0)))
 
459
    def file_revisions(self, file_id):
 
460
        self.ensure_revision_trees()
 
461
        if self.this_rev_id is None:
 
462
            if self.this_basis_tree.get_file_sha1(file_id) != \
 
463
                self.this_tree.get_file_sha1(file_id):
 
464
                raise errors.WorkingTreeNotRevision(self.this_tree)
 
465
 
 
466
        trees = (self.this_basis_tree, self.other_tree)
 
467
        return [tree.get_file_revision(file_id) for tree in trees]
 
468
 
 
469
    @deprecated_method(deprecated_in((2, 1, 0)))
 
470
    def check_basis(self, check_clean, require_commits=True):
 
471
        if self.this_basis is None and require_commits is True:
 
472
            raise errors.BzrCommandError(
 
473
                "This branch has no commits."
 
474
                " (perhaps you would prefer 'bzr pull')")
 
475
        if check_clean:
 
476
            self.compare_basis()
 
477
            if self.this_basis != self.this_rev_id:
 
478
                raise errors.UncommittedChanges(self.this_tree)
 
479
 
 
480
    @deprecated_method(deprecated_in((2, 1, 0)))
 
481
    def compare_basis(self):
 
482
        try:
 
483
            basis_tree = self.revision_tree(self.this_tree.last_revision())
 
484
        except errors.NoSuchRevision:
 
485
            basis_tree = self.this_tree.basis_tree()
 
486
        if not self.this_tree.has_changes(basis_tree):
 
487
            self.this_rev_id = self.this_basis
 
488
 
457
489
    def set_interesting_files(self, file_list):
458
490
        self.interesting_files = file_list
459
491
 
504
536
                raise errors.NoCommits(self.other_branch)
505
537
        if self.other_rev_id is not None:
506
538
            self._cached_trees[self.other_rev_id] = self.other_tree
507
 
        self._maybe_fetch(self.other_branch, self.this_branch, self.other_basis)
 
539
        self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
508
540
 
509
541
    def set_other_revision(self, revision_id, other_branch):
510
542
        """Set 'other' based on a branch and revision id
612
644
            self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
613
645
 
614
646
    def make_merger(self):
615
 
        kwargs = {'working_tree': self.this_tree, 'this_tree': self.this_tree,
 
647
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
616
648
                  'other_tree': self.other_tree,
617
649
                  'interesting_ids': self.interesting_ids,
618
650
                  'interesting_files': self.interesting_files,
619
651
                  'this_branch': self.this_branch,
620
 
                  'other_branch': self.other_branch,
621
652
                  'do_merge': False}
622
653
        if self.merge_type.requires_base:
623
654
            kwargs['base_tree'] = self.base_tree
649
680
        merge = self.make_merger()
650
681
        if self.other_branch is not None:
651
682
            self.other_branch.update_references(self.this_branch)
652
 
        for hook in Merger.hooks['pre_merge']:
653
 
            hook(merge)
654
683
        merge.do_merge()
655
 
        for hook in Merger.hooks['post_merge']:
656
 
            hook(merge)
657
684
        if self.recurse == 'down':
658
685
            for relpath, file_id in self.this_tree.iter_references():
659
686
                sub_tree = self.this_tree.get_nested_tree(file_id, relpath)
686
713
        merge = operation.run_simple()
687
714
        if len(merge.cooked_conflicts) == 0:
688
715
            if not self.ignore_zero and not trace.is_quiet():
689
 
                trace.note(gettext("All changes applied successfully."))
 
716
                trace.note("All changes applied successfully.")
690
717
        else:
691
 
            trace.note(gettext("%d conflicts encountered.")
 
718
            trace.note("%d conflicts encountered."
692
719
                       % len(merge.cooked_conflicts))
693
720
 
694
721
        return len(merge.cooked_conflicts)
726
753
                 interesting_ids=None, reprocess=False, show_base=False,
727
754
                 pb=None, pp=None, change_reporter=None,
728
755
                 interesting_files=None, do_merge=True,
729
 
                 cherrypick=False, lca_trees=None, this_branch=None,
730
 
                 other_branch=None):
 
756
                 cherrypick=False, lca_trees=None, this_branch=None):
731
757
        """Initialize the merger object and perform the merge.
732
758
 
733
759
        :param working_tree: The working tree to apply the merge to
736
762
        :param other_tree: The other tree to merge changes from
737
763
        :param this_branch: The branch associated with this_tree.  Defaults to
738
764
            this_tree.branch if not supplied.
739
 
        :param other_branch: The branch associated with other_tree, if any.
740
765
        :param interesting_ids: The file_ids of files that should be
741
766
            participate in the merge.  May not be combined with
742
767
            interesting_files.
764
789
            this_branch = this_tree.branch
765
790
        self.interesting_ids = interesting_ids
766
791
        self.interesting_files = interesting_files
767
 
        self.working_tree = working_tree
768
 
        self.this_tree = this_tree
 
792
        self.this_tree = working_tree
769
793
        self.base_tree = base_tree
770
794
        self.other_tree = other_tree
771
795
        self.this_branch = this_branch
772
 
        self.other_branch = other_branch
773
796
        self._raw_conflicts = []
774
797
        self.cooked_conflicts = []
775
798
        self.reprocess = reprocess
791
814
 
792
815
    def do_merge(self):
793
816
        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()
 
817
        self.this_tree.lock_tree_write()
797
818
        operation.add_cleanup(self.this_tree.unlock)
798
819
        self.base_tree.lock_read()
799
820
        operation.add_cleanup(self.base_tree.unlock)
802
823
        operation.run()
803
824
 
804
825
    def _do_merge(self, operation):
805
 
        self.tt = transform.TreeTransform(self.working_tree, None)
 
826
        self.tt = transform.TreeTransform(self.this_tree, None)
806
827
        operation.add_cleanup(self.tt.finalize)
807
828
        self._compute_transform()
808
829
        results = self.tt.apply(no_conflicts=True)
809
830
        self.write_modified(results)
810
831
        try:
811
 
            self.working_tree.add_conflicts(self.cooked_conflicts)
 
832
            self.this_tree.add_conflicts(self.cooked_conflicts)
812
833
        except errors.UnsupportedOperation:
813
834
            pass
814
835
 
821
842
        return operation.run_simple()
822
843
 
823
844
    def _make_preview_transform(self):
824
 
        self.tt = transform.TransformPreview(self.working_tree)
 
845
        self.tt = transform.TransformPreview(self.this_tree)
825
846
        self._compute_transform()
826
847
        return self.tt
827
848
 
832
853
        else:
833
854
            entries = self._entries_lca()
834
855
            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
856
        child_pb = ui.ui_factory.nested_progress_bar()
841
857
        try:
 
858
            factories = Merger.hooks['merge_file_content']
 
859
            hooks = [factory(self) for factory in factories] + [self]
 
860
            self.active_hooks = [hook for hook in hooks if hook is not None]
842
861
            for num, (file_id, changed, parents3, names3,
843
862
                      executable3) in enumerate(entries):
844
 
                # Try merging each entry
845
 
                child_pb.update(gettext('Preparing file merge'),
846
 
                                num, len(entries))
 
863
                child_pb.update('Preparing file merge', num, len(entries))
847
864
                self._merge_names(file_id, parents3, names3, resolver=resolver)
848
865
                if changed:
849
866
                    file_status = self._do_merge_contents(file_id)
1091
1108
        other_root = self.tt.trans_id_file_id(other_root_file_id)
1092
1109
        if other_root == self.tt.root:
1093
1110
            return
1094
 
        if self.this_tree.has_id(
1095
 
            self.other_tree.get_root_id()):
 
1111
        if self.this_tree.inventory.has_id(
 
1112
            self.other_tree.inventory.root.file_id):
1096
1113
            # the other tree's root is a non-root in the current tree (as
1097
1114
            # when a previously unrelated branch is merged into another)
1098
1115
            return
1126
1143
    def write_modified(self, results):
1127
1144
        modified_hashes = {}
1128
1145
        for path in results.modified_paths:
1129
 
            file_id = self.working_tree.path2id(self.working_tree.relpath(path))
 
1146
            file_id = self.this_tree.path2id(self.this_tree.relpath(path))
1130
1147
            if file_id is None:
1131
1148
                continue
1132
 
            hash = self.working_tree.get_file_sha1(file_id)
 
1149
            hash = self.this_tree.get_file_sha1(file_id)
1133
1150
            if hash is None:
1134
1151
                continue
1135
1152
            modified_hashes[file_id] = hash
1136
 
        self.working_tree.set_merge_modified(modified_hashes)
 
1153
        self.this_tree.set_merge_modified(modified_hashes)
1137
1154
 
1138
1155
    @staticmethod
1139
1156
    def parent(entry, file_id):
1363
1380
        # We have a hypothetical conflict, but if we have files, then we
1364
1381
        # can try to merge the content
1365
1382
        trans_id = self.tt.trans_id_file_id(file_id)
1366
 
        params = MergeFileHookParams(self, file_id, trans_id, this_pair[0],
 
1383
        params = MergeHookParams(self, file_id, trans_id, this_pair[0],
1367
1384
            other_pair[0], winner)
1368
1385
        hooks = self.active_hooks
1369
1386
        hook_status = 'not_applicable'
1930
1947
            entries = self._entries_to_incorporate()
1931
1948
            entries = list(entries)
1932
1949
            for num, (entry, parent_id) in enumerate(entries):
1933
 
                child_pb.update(gettext('Preparing file merge'), num, len(entries))
 
1950
                child_pb.update('Preparing file merge', num, len(entries))
1934
1951
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
1935
1952
                trans_id = transform.new_by_entry(self.tt, entry,
1936
1953
                    parent_trans_id, self.other_tree)
1948
1965
            raise PathNotInTree(self._source_subpath, "Source tree")
1949
1966
        subdir = other_inv[subdir_id]
1950
1967
        parent_in_target = osutils.dirname(self._target_subdir)
1951
 
        target_id = self.this_tree.path2id(parent_in_target)
 
1968
        target_id = self.this_tree.inventory.path2id(parent_in_target)
1952
1969
        if target_id is None:
1953
1970
            raise PathNotInTree(self._target_subdir, "Target tree")
1954
1971
        name_in_target = osutils.basename(self._target_subdir)
1955
1972
        merge_into_root = subdir.copy()
1956
1973
        merge_into_root.name = name_in_target
1957
 
        if self.this_tree.has_id(merge_into_root.file_id):
 
1974
        if self.this_tree.inventory.has_id(merge_into_root.file_id):
1958
1975
            # Give the root a new file-id.
1959
1976
            # This can happen fairly easily if the directory we are
1960
1977
            # incorporating is the root, and both trees have 'TREE_ROOT' as
2020
2037
    merger.set_base_revision(get_revision_id(), this_branch)
2021
2038
    return merger.do_merge()
2022
2039
 
2023
 
 
2024
 
merge_type_registry = registry.Registry()
2025
 
merge_type_registry.register('diff3', Diff3Merger,
2026
 
                             "Merge using external diff3.")
2027
 
merge_type_registry.register('lca', LCAMerger,
2028
 
                             "LCA-newness merge.")
2029
 
merge_type_registry.register('merge3', Merge3Merger,
2030
 
                             "Native diff3-style merge.")
2031
 
merge_type_registry.register('weave', WeaveMerger,
2032
 
                             "Weave-based merge.")
2033
 
 
2034
 
 
2035
2040
def get_merge_type_registry():
2036
 
    """Merge type registry was previously in bzrlib.option
 
2041
    """Merge type registry is in bzrlib.option to avoid circular imports.
2037
2042
 
2038
 
    This method provides a backwards compatible way to retrieve it.
 
2043
    This method provides a sanctioned way to retrieve it.
2039
2044
    """
2040
 
    return merge_type_registry
 
2045
    from bzrlib import option
 
2046
    return option._merge_type_registry
2041
2047
 
2042
2048
 
2043
2049
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):