~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Andrew Bennetts
  • Date: 2007-08-02 06:40:58 UTC
  • mfrom: (2666 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2668.
  • Revision ID: andrew.bennetts@canonical.com-20070802064058-09eblz1qbc01fcr3
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
from bzrlib import (
23
23
    errors,
24
24
    osutils,
 
25
    patiencediff,
25
26
    registry,
26
27
    revision as _mod_revision,
27
28
    )
49
50
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
50
51
                              conflict_pass, FinalPaths, create_by_entry,
51
52
                              unique_add, ROOT_PARENT)
52
 
from bzrlib.versionedfile import WeaveMerge
 
53
from bzrlib.versionedfile import PlanWeaveMerge
53
54
from bzrlib import ui
54
55
 
55
56
# TODO: Report back as changes are merged in
88
89
        self.change_reporter = change_reporter
89
90
        self._cached_trees = {}
90
91
 
 
92
    @staticmethod
 
93
    def from_uncommitted(tree, other_tree, pb):
 
94
        """Return a Merger for uncommitted changes in other_tree.
 
95
 
 
96
        :param tree: The tree to merge into
 
97
        :param other_tree: The tree to get uncommitted changes from
 
98
        :param pb: A progress indicator
 
99
        """
 
100
        merger = Merger(tree.branch, other_tree, other_tree.basis_tree(), tree,
 
101
                        pb)
 
102
        merger.base_rev_id = merger.base_tree.get_revision_id()
 
103
        merger.other_rev_id = None
 
104
        return merger
 
105
 
 
106
    @classmethod
 
107
    def from_mergeable(klass, tree, mergeable, pb):
 
108
        """Return a Merger for a bundle or merge directive.
 
109
 
 
110
        :param tree: The tree to merge changes into
 
111
        :param mergeable: A merge directive or bundle
 
112
        :param pb: A progress indicator
 
113
        """
 
114
        mergeable.install_revisions(tree.branch.repository)
 
115
        base_revision_id, other_revision_id, verified =\
 
116
            mergeable.get_merge_request(tree.branch.repository)
 
117
        if (base_revision_id != _mod_revision.NULL_REVISION and
 
118
            tree.branch.repository.get_graph().is_ancestor(
 
119
            base_revision_id, tree.branch.last_revision())):
 
120
            base_revision_id = None
 
121
        merger = klass.from_revision_ids(pb, tree, other_revision_id,
 
122
                                         base_revision_id)
 
123
        return merger, verified
 
124
 
 
125
    @staticmethod
 
126
    def from_revision_ids(pb, this, other, base=None, other_branch=None,
 
127
                          base_branch=None):
 
128
        """Return a Merger for revision-ids.
 
129
 
 
130
        :param tree: The tree to merge changes into
 
131
        :param other: The revision-id to use as OTHER
 
132
        :param base: The revision-id to use as BASE.  If not specified, will
 
133
            be auto-selected.
 
134
        :param other_branch: A branch containing the other revision-id.  If
 
135
            not supplied, this.branch is used.
 
136
        :param base_branch: A branch containing the base revision-id.  If
 
137
            not supplied, other_branch or this.branch will be used.
 
138
        :param pb: A progress indicator
 
139
        """
 
140
        merger = Merger(this.branch, this_tree=this, pb=pb)
 
141
        if other_branch is None:
 
142
            other_branch = this.branch
 
143
        merger.set_other_revision(other, other_branch)
 
144
        if base is None:
 
145
            merger.find_base()
 
146
        else:
 
147
            if base_branch is None:
 
148
                base_branch = other_branch
 
149
            merger.set_base_revision(base, base_branch)
 
150
        return merger
 
151
 
91
152
    def revision_tree(self, revision_id, branch=None):
92
153
        if revision_id not in self._cached_trees:
93
154
            if branch is None:
99
160
            self._cached_trees[revision_id] = tree
100
161
        return self._cached_trees[revision_id]
101
162
 
102
 
    def _get_tree(self, treespec):
 
163
    def _get_tree(self, treespec, possible_transports=None):
103
164
        from bzrlib import workingtree
104
165
        location, revno = treespec
105
166
        if revno is None:
106
167
            tree = workingtree.WorkingTree.open_containing(location)[0]
107
168
            return tree.branch, tree
108
 
        branch = Branch.open_containing(location)[0]
 
169
        branch = Branch.open_containing(location, possible_transports)[0]
109
170
        if revno == -1:
110
171
            revision_id = branch.last_revision()
111
172
        else:
163
224
        self.interesting_files = file_list
164
225
 
165
226
    def set_pending(self):
166
 
        if not self.base_is_ancestor or not self.base_is_other_ancestor:
 
227
        if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
167
228
            return
168
229
        self._add_parent()
169
230
 
186
247
                if tree is not None:
187
248
                    tree.unlock()
188
249
 
189
 
    def set_other(self, other_revision):
 
250
    def set_other(self, other_revision, possible_transports=None):
190
251
        """Set the revision and tree to merge from.
191
252
 
192
253
        This sets the other_tree, other_rev_id, other_basis attributes.
193
254
 
194
255
        :param other_revision: The [path, revision] list to merge from.
195
256
        """
196
 
        self.other_branch, self.other_tree = self._get_tree(other_revision)
 
257
        self.other_branch, self.other_tree = self._get_tree(other_revision,
 
258
                                                            possible_transports)
197
259
        if other_revision[1] == -1:
198
260
            self.other_rev_id = _mod_revision.ensure_null(
199
261
                self.other_branch.last_revision())
224
286
        self.other_tree = self.revision_tree(revision_id)
225
287
        self.other_basis = revision_id
226
288
 
 
289
    def set_base_revision(self, revision_id, branch):
 
290
        """Set 'base' based on a branch and revision id
 
291
 
 
292
        :param revision_id: The revision to use for a tree
 
293
        :param branch: The branch containing this tree
 
294
        """
 
295
        self.base_rev_id = revision_id
 
296
        self.base_branch = branch
 
297
        self._maybe_fetch(branch, self.this_branch, revision_id)
 
298
        self.base_tree = self.revision_tree(revision_id)
 
299
        self.base_is_ancestor = is_ancestor(self.this_basis,
 
300
                                            self.base_rev_id,
 
301
                                            self.this_branch)
 
302
        self.base_is_other_ancestor = is_ancestor(self.other_basis,
 
303
                                                  self.base_rev_id,
 
304
                                                  self.this_branch)
 
305
 
227
306
    def _maybe_fetch(self, source, target, revision_id):
228
307
        if (source.repository.bzrdir.root_transport.base !=
229
308
            target.repository.bzrdir.root_transport.base):
901
980
                 interesting_ids=None, pb=DummyProgress(), pp=None,
902
981
                 reprocess=False, change_reporter=None,
903
982
                 interesting_files=None):
904
 
        self.this_revision_tree = self._get_revision_tree(this_tree)
905
 
        self.other_revision_tree = self._get_revision_tree(other_tree)
906
983
        super(WeaveMerger, self).__init__(working_tree, this_tree, 
907
984
                                          base_tree, other_tree, 
908
985
                                          interesting_ids=interesting_ids, 
909
986
                                          pb=pb, pp=pp, reprocess=reprocess,
910
987
                                          change_reporter=change_reporter)
911
988
 
912
 
    def _get_revision_tree(self, tree):
913
 
        """Return a revision tree related to this tree.
914
 
        If the tree is a WorkingTree, the basis will be returned.
915
 
        """
916
 
        if getattr(tree, 'get_weave', False) is False:
917
 
            # If we have a WorkingTree, try using the basis
918
 
            return tree.branch.basis_tree()
919
 
        else:
920
 
            return tree
921
 
 
922
 
    def _check_file(self, file_id):
923
 
        """Check that the revision tree's version of the file matches."""
924
 
        for tree, rt in ((self.this_tree, self.this_revision_tree), 
925
 
                         (self.other_tree, self.other_revision_tree)):
926
 
            if rt is tree:
927
 
                continue
928
 
            if tree.get_file_sha1(file_id) != rt.get_file_sha1(file_id):
929
 
                raise WorkingTreeNotRevision(self.this_tree)
930
 
 
931
989
    def _merged_lines(self, file_id):
932
990
        """Generate the merged lines.
933
991
        There is no distinction between lines that are meant to contain <<<<<<<
934
992
        and conflicts.
935
993
        """
936
 
        weave = self.this_revision_tree.get_weave(file_id)
937
 
        this_revision_id = self.this_revision_tree.inventory[file_id].revision
938
 
        other_revision_id = \
939
 
            self.other_revision_tree.inventory[file_id].revision
940
 
        wm = WeaveMerge(weave, this_revision_id, other_revision_id, 
941
 
                        '<<<<<<< TREE\n', '>>>>>>> MERGE-SOURCE\n')
942
 
        return wm.merge_lines(self.reprocess)
 
994
        plan = self.this_tree.plan_file_merge(file_id, self.other_tree)
 
995
        textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
 
996
            '>>>>>>> MERGE-SOURCE\n')
 
997
        return textmerge.merge_lines(self.reprocess)
943
998
 
944
999
    def text_merge(self, file_id, trans_id):
945
1000
        """Perform a (weave) text merge for a given file and file-id.
946
1001
        If conflicts are encountered, .THIS and .OTHER files will be emitted,
947
1002
        and a conflict will be noted.
948
1003
        """
949
 
        self._check_file(file_id)
950
1004
        lines, conflicts = self._merged_lines(file_id)
951
1005
        lines = list(lines)
952
1006
        # Note we're checking whether the OUTPUT is binary in this case, 
1048
1102
    """
1049
1103
    from bzrlib import option
1050
1104
    return option._merge_type_registry
 
1105
 
 
1106
 
 
1107
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
 
1108
    def status_a(revision, text):
 
1109
        if revision in ancestors_b:
 
1110
            return 'killed-b', text
 
1111
        else:
 
1112
            return 'new-a', text
 
1113
 
 
1114
    def status_b(revision, text):
 
1115
        if revision in ancestors_a:
 
1116
            return 'killed-a', text
 
1117
        else:
 
1118
            return 'new-b', text
 
1119
 
 
1120
    plain_a = [t for (a, t) in annotated_a]
 
1121
    plain_b = [t for (a, t) in annotated_b]
 
1122
    matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
 
1123
    blocks = matcher.get_matching_blocks()
 
1124
    a_cur = 0
 
1125
    b_cur = 0
 
1126
    for ai, bi, l in blocks:
 
1127
        # process all mismatched sections
 
1128
        # (last mismatched section is handled because blocks always
 
1129
        # includes a 0-length last block)
 
1130
        for revision, text in annotated_a[a_cur:ai]:
 
1131
            yield status_a(revision, text)
 
1132
        for revision, text in annotated_b[b_cur:bi]:
 
1133
            yield status_b(revision, text)
 
1134
 
 
1135
        # and now the matched section
 
1136
        a_cur = ai + l
 
1137
        b_cur = bi + l
 
1138
        for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
 
1139
            assert text_a == text_b
 
1140
            yield "unchanged", text_a