~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

Add source index to the index iteration API to allow mapping back to the origin of retrieved data.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
 
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
17
 
18
18
import os
19
19
import errno
20
 
from shutil import rmtree
21
 
from tempfile import mkdtemp
 
20
import warnings
22
21
 
23
 
import bzrlib
 
22
from bzrlib import (
 
23
    errors,
 
24
    osutils,
 
25
    patiencediff,
 
26
    registry,
 
27
    revision as _mod_revision,
 
28
    )
24
29
from bzrlib.branch import Branch
25
 
from bzrlib.delta import compare_trees
 
30
from bzrlib.conflicts import ConflictList, Conflict
26
31
from bzrlib.errors import (BzrCommandError,
27
32
                           BzrError,
28
33
                           NoCommonAncestor,
32
37
                           NotBranchError,
33
38
                           NotVersionedError,
34
39
                           UnrelatedBranches,
 
40
                           UnsupportedOperation,
35
41
                           WorkingTreeNotRevision,
 
42
                           BinaryFile,
36
43
                           )
37
44
from bzrlib.merge3 import Merge3
38
 
import bzrlib.osutils
39
45
from bzrlib.osutils import rename, pathjoin
40
 
from progress import DummyProgress
41
 
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
42
 
from bzrlib.symbol_versioning import *
 
46
from progress import DummyProgress, ProgressPhase
 
47
from bzrlib.revision import (is_ancestor, NULL_REVISION, ensure_null)
 
48
from bzrlib.textfile import check_text_lines
43
49
from bzrlib.trace import mutter, warning, note
44
50
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
45
 
                              conflicts_strings, FinalPaths, create_by_entry,
46
 
                              unique_add)
 
51
                              conflict_pass, FinalPaths, create_by_entry,
 
52
                              unique_add, ROOT_PARENT)
 
53
from bzrlib.versionedfile import PlanWeaveMerge
 
54
from bzrlib import ui
47
55
 
48
56
# TODO: Report back as changes are merged in
49
57
 
50
 
def _get_tree(treespec, local_branch=None):
51
 
    location, revno = treespec
52
 
    branch = Branch.open_containing(location)[0]
53
 
    if revno is None:
54
 
        revision = None
55
 
    elif revno == -1:
56
 
        revision = branch.last_revision()
57
 
    else:
58
 
        revision = branch.get_rev_id(revno)
59
 
        if revision is None:
60
 
            revision = NULL_REVISION
61
 
    return branch, _get_revid_tree(branch, revision, local_branch)
62
 
 
63
 
 
64
 
def _get_revid_tree(branch, revision, local_branch):
65
 
    if revision is None:
66
 
        base_tree = branch.bzrdir.open_workingtree()
67
 
    else:
68
 
        if local_branch is not None:
69
 
            if local_branch.base != branch.base:
70
 
                local_branch.fetch(branch, revision)
71
 
            base_tree = local_branch.repository.revision_tree(revision)
72
 
        else:
73
 
            base_tree = branch.repository.revision_tree(revision)
74
 
    return base_tree
75
 
 
76
58
 
77
59
def transform_tree(from_tree, to_tree, interesting_ids=None):
78
60
    merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
80
62
 
81
63
 
82
64
class Merger(object):
83
 
    def __init__(self, this_branch, other_tree=None, base_tree=None, 
84
 
                 this_tree=None, pb=DummyProgress()):
 
65
    def __init__(self, this_branch, other_tree=None, base_tree=None,
 
66
                 this_tree=None, pb=DummyProgress(), change_reporter=None,
 
67
                 recurse='down'):
85
68
        object.__init__(self)
86
69
        assert this_tree is not None, "this_tree is required"
87
70
        self.this_branch = this_branch
88
 
        self.this_basis = this_branch.last_revision()
 
71
        self.this_basis = _mod_revision.ensure_null(
 
72
            this_branch.last_revision())
89
73
        self.this_rev_id = None
90
74
        self.this_tree = this_tree
91
75
        self.this_revision_tree = None
92
76
        self.this_basis_tree = None
93
77
        self.other_tree = other_tree
 
78
        self.other_branch = None
94
79
        self.base_tree = base_tree
95
80
        self.ignore_zero = False
96
81
        self.backup_files = False
97
82
        self.interesting_ids = None
 
83
        self.interesting_files = None
98
84
        self.show_base = False
99
85
        self.reprocess = False
100
 
        self._pb = pb 
101
 
 
102
 
    def revision_tree(self, revision_id):
103
 
        return self.this_branch.repository.revision_tree(revision_id)
 
86
        self._pb = pb
 
87
        self.pp = None
 
88
        self.recurse = recurse
 
89
        self.change_reporter = change_reporter
 
90
        self._cached_trees = {}
 
91
 
 
92
    def revision_tree(self, revision_id, branch=None):
 
93
        if revision_id not in self._cached_trees:
 
94
            if branch is None:
 
95
                branch = self.this_branch
 
96
            try:
 
97
                tree = self.this_tree.revision_tree(revision_id)
 
98
            except errors.NoSuchRevisionInTree:
 
99
                tree = branch.repository.revision_tree(revision_id)
 
100
            self._cached_trees[revision_id] = tree
 
101
        return self._cached_trees[revision_id]
 
102
 
 
103
    def _get_tree(self, treespec, possible_transports=None):
 
104
        from bzrlib import workingtree
 
105
        location, revno = treespec
 
106
        if revno is None:
 
107
            tree = workingtree.WorkingTree.open_containing(location)[0]
 
108
            return tree.branch, tree
 
109
        branch = Branch.open_containing(location, possible_transports)[0]
 
110
        if revno == -1:
 
111
            revision_id = branch.last_revision()
 
112
        else:
 
113
            revision_id = branch.get_rev_id(revno)
 
114
        revision_id = ensure_null(revision_id)
 
115
        return branch, self.revision_tree(revision_id, branch)
104
116
 
105
117
    def ensure_revision_trees(self):
106
118
        if self.this_revision_tree is None:
107
 
            self.this_basis_tree = self.this_branch.repository.revision_tree(
108
 
                self.this_basis)
 
119
            self.this_basis_tree = self.revision_tree(self.this_basis)
109
120
            if self.this_basis == self.this_rev_id:
110
121
                self.this_revision_tree = self.this_basis_tree
111
122
 
112
123
        if self.other_rev_id is None:
113
124
            other_basis_tree = self.revision_tree(self.other_basis)
114
 
            changes = compare_trees(self.other_tree, other_basis_tree)
 
125
            changes = other_basis_tree.changes_from(self.other_tree)
115
126
            if changes.has_changed():
116
127
                raise WorkingTreeNotRevision(self.this_tree)
117
 
            other_rev_id = other_basis
 
128
            other_rev_id = self.other_basis
118
129
            self.other_tree = other_basis_tree
119
130
 
120
131
    def file_revisions(self, file_id):
131
142
        trees = (self.this_basis_tree, self.other_tree)
132
143
        return [get_id(tree, file_id) for tree in trees]
133
144
 
134
 
    def check_basis(self, check_clean):
135
 
        if self.this_basis is None:
136
 
            raise BzrCommandError("This branch has no commits")
 
145
    def check_basis(self, check_clean, require_commits=True):
 
146
        if self.this_basis is None and require_commits is True:
 
147
            raise BzrCommandError("This branch has no commits."
 
148
                                  " (perhaps you would prefer 'bzr pull')")
137
149
        if check_clean:
138
150
            self.compare_basis()
139
151
            if self.this_basis != self.this_rev_id:
140
152
                raise BzrCommandError("Working tree has uncommitted changes.")
141
153
 
142
154
    def compare_basis(self):
143
 
        changes = compare_trees(self.this_tree, 
144
 
                                self.this_tree.basis_tree(), False)
 
155
        try:
 
156
            basis_tree = self.revision_tree(self.this_tree.last_revision())
 
157
        except errors.RevisionNotPresent:
 
158
            basis_tree = self.this_tree.basis_tree()
 
159
        changes = self.this_tree.changes_from(basis_tree)
145
160
        if not changes.has_changed():
146
161
            self.this_rev_id = self.this_basis
147
162
 
148
163
    def set_interesting_files(self, file_list):
149
 
        try:
150
 
            self._set_interesting_files(file_list)
151
 
        except NotVersionedError, e:
152
 
            raise BzrCommandError("%s is not a source file in any"
153
 
                                      " tree." % e.path)
154
 
 
155
 
    def _set_interesting_files(self, file_list):
156
 
        """Set the list of interesting ids from a list of files."""
157
 
        if file_list is None:
158
 
            self.interesting_ids = None
159
 
            return
160
 
 
161
 
        interesting_ids = set()
162
 
        for path in file_list:
163
 
            found_id = False
164
 
            for tree in (self.this_tree, self.base_tree, self.other_tree):
165
 
                file_id = tree.inventory.path2id(path)
166
 
                if file_id is not None:
167
 
                    interesting_ids.add(file_id)
168
 
                    found_id = True
169
 
            if not found_id:
170
 
                raise NotVersionedError(path=path)
171
 
        self.interesting_ids = interesting_ids
 
164
        self.interesting_files = file_list
172
165
 
173
166
    def set_pending(self):
174
 
        if not self.base_is_ancestor:
175
 
            return
176
 
        if self.other_rev_id is None:
177
 
            return
178
 
        ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
179
 
        if self.other_rev_id in ancestry:
180
 
            return
181
 
        self.this_tree.add_pending_merge(self.other_rev_id)
182
 
 
183
 
    def set_other(self, other_revision):
184
 
        other_branch, self.other_tree = _get_tree(other_revision, 
185
 
                                                  self.this_branch)
 
167
        if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
 
168
            return
 
169
        self._add_parent()
 
170
 
 
171
    def _add_parent(self):
 
172
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
 
173
        new_parent_trees = []
 
174
        for revision_id in new_parents:
 
175
            try:
 
176
                tree = self.revision_tree(revision_id)
 
177
            except errors.RevisionNotPresent:
 
178
                tree = None
 
179
            else:
 
180
                tree.lock_read()
 
181
            new_parent_trees.append((revision_id, tree))
 
182
        try:
 
183
            self.this_tree.set_parent_trees(new_parent_trees,
 
184
                                            allow_leftmost_as_ghost=True)
 
185
        finally:
 
186
            for _revision_id, tree in new_parent_trees:
 
187
                if tree is not None:
 
188
                    tree.unlock()
 
189
 
 
190
    def set_other(self, other_revision, possible_transports=None):
 
191
        """Set the revision and tree to merge from.
 
192
 
 
193
        This sets the other_tree, other_rev_id, other_basis attributes.
 
194
 
 
195
        :param other_revision: The [path, revision] list to merge from.
 
196
        """
 
197
        self.other_branch, self.other_tree = self._get_tree(other_revision,
 
198
                                                            possible_transports)
186
199
        if other_revision[1] == -1:
187
 
            self.other_rev_id = other_branch.last_revision()
188
 
            if self.other_rev_id is None:
189
 
                raise NoCommits(other_branch)
 
200
            self.other_rev_id = _mod_revision.ensure_null(
 
201
                self.other_branch.last_revision())
 
202
            if _mod_revision.is_null(self.other_rev_id):
 
203
                raise NoCommits(self.other_branch)
190
204
            self.other_basis = self.other_rev_id
191
205
        elif other_revision[1] is not None:
192
 
            self.other_rev_id = other_branch.get_rev_id(other_revision[1])
 
206
            self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
193
207
            self.other_basis = self.other_rev_id
194
208
        else:
195
209
            self.other_rev_id = None
196
 
            self.other_basis = other_branch.last_revision()
 
210
            self.other_basis = self.other_branch.last_revision()
197
211
            if self.other_basis is None:
198
 
                raise NoCommits(other_branch)
199
 
        if other_branch.base != self.this_branch.base:
200
 
            self.this_branch.fetch(other_branch, last_revision=self.other_basis)
 
212
                raise NoCommits(self.other_branch)
 
213
        if self.other_rev_id is not None:
 
214
            self._cached_trees[self.other_rev_id] = self.other_tree
 
215
        self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
 
216
 
 
217
    def set_other_revision(self, revision_id, other_branch):
 
218
        """Set 'other' based on a branch and revision id
 
219
 
 
220
        :param revision_id: The revision to use for a tree
 
221
        :param other_branch: The branch containing this tree
 
222
        """
 
223
        self.other_rev_id = revision_id
 
224
        self.other_branch = other_branch
 
225
        self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
 
226
        self.other_tree = self.revision_tree(revision_id)
 
227
        self.other_basis = revision_id
 
228
 
 
229
    def set_base_revision(self, revision_id, branch):
 
230
        """Set 'base' based on a branch and revision id
 
231
 
 
232
        :param revision_id: The revision to use for a tree
 
233
        :param branch: The branch containing this tree
 
234
        """
 
235
        self.base_rev_id = revision_id
 
236
        self.base_branch = branch
 
237
        self._maybe_fetch(branch, self.this_branch, revision_id)
 
238
        self.base_tree = self.revision_tree(revision_id)
 
239
        self.base_is_ancestor = is_ancestor(self.this_basis,
 
240
                                            self.base_rev_id,
 
241
                                            self.this_branch)
 
242
        self.base_is_other_ancestor = is_ancestor(self.other_basis,
 
243
                                                  self.base_rev_id,
 
244
                                                  self.this_branch)
 
245
 
 
246
    def _maybe_fetch(self, source, target, revision_id):
 
247
        if (source.repository.bzrdir.root_transport.base !=
 
248
            target.repository.bzrdir.root_transport.base):
 
249
            target.fetch(source, revision_id)
 
250
 
 
251
    def find_base(self):
 
252
        this_repo = self.this_branch.repository
 
253
        graph = this_repo.get_graph()
 
254
        revisions = [ensure_null(self.this_basis),
 
255
                     ensure_null(self.other_basis)]
 
256
        if NULL_REVISION in revisions:
 
257
            self.base_rev_id = NULL_REVISION
 
258
        else:
 
259
            self.base_rev_id = graph.find_unique_lca(*revisions)
 
260
            if self.base_rev_id == NULL_REVISION:
 
261
                raise UnrelatedBranches()
 
262
        self.base_tree = self.revision_tree(self.base_rev_id)
 
263
        self.base_is_ancestor = True
 
264
        self.base_is_other_ancestor = True
201
265
 
202
266
    def set_base(self, base_revision):
 
267
        """Set the base revision to use for the merge.
 
268
 
 
269
        :param base_revision: A 2-list containing a path and revision number.
 
270
        """
203
271
        mutter("doing merge() with no base_revision specified")
204
272
        if base_revision == [None, None]:
205
 
            try:
206
 
                self.base_rev_id = common_ancestor(self.this_basis, 
207
 
                                                   self.other_basis, 
208
 
                                                   self.this_branch.repository,
209
 
                                                   self._pb)
210
 
            except NoCommonAncestor:
211
 
                raise UnrelatedBranches()
212
 
            self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
213
 
                                            None)
214
 
            self.base_is_ancestor = True
 
273
            self.find_base()
215
274
        else:
216
 
            base_branch, self.base_tree = _get_tree(base_revision)
 
275
            base_branch, self.base_tree = self._get_tree(base_revision)
217
276
            if base_revision[1] == -1:
218
277
                self.base_rev_id = base_branch.last_revision()
219
278
            elif base_revision[1] is None:
220
 
                self.base_rev_id = None
 
279
                self.base_rev_id = _mod_revision.NULL_REVISION
221
280
            else:
222
 
                self.base_rev_id = base_branch.get_rev_id(base_revision[1])
223
 
            self.this_branch.fetch(base_branch)
 
281
                self.base_rev_id = _mod_revision.ensure_null(
 
282
                    base_branch.get_rev_id(base_revision[1]))
 
283
            self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
224
284
            self.base_is_ancestor = is_ancestor(self.this_basis, 
225
285
                                                self.base_rev_id,
226
286
                                                self.this_branch)
 
287
            self.base_is_other_ancestor = is_ancestor(self.other_basis,
 
288
                                                      self.base_rev_id,
 
289
                                                      self.this_branch)
227
290
 
228
291
    def do_merge(self):
229
 
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree, 
230
 
                  'other_tree': self.other_tree}
 
292
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
 
293
                  'other_tree': self.other_tree,
 
294
                  'interesting_ids': self.interesting_ids,
 
295
                  'interesting_files': self.interesting_files,
 
296
                  'pp': self.pp}
231
297
        if self.merge_type.requires_base:
232
298
            kwargs['base_tree'] = self.base_tree
233
299
        if self.merge_type.supports_reprocess:
234
300
            kwargs['reprocess'] = self.reprocess
235
301
        elif self.reprocess:
236
 
            raise BzrError("Reprocess is not supported for this merge"
237
 
                                  " type. %s" % merge_type)
 
302
            raise BzrError("Conflict reduction is not supported for merge"
 
303
                                  " type %s." % self.merge_type)
238
304
        if self.merge_type.supports_show_base:
239
305
            kwargs['show_base'] = self.show_base
240
306
        elif self.show_base:
241
307
            raise BzrError("Showing base is not supported for this"
242
308
                                  " merge type. %s" % self.merge_type)
243
 
        merge = self.merge_type(pb=self._pb, **kwargs)
 
309
        self.this_tree.lock_tree_write()
 
310
        if self.base_tree is not None:
 
311
            self.base_tree.lock_read()
 
312
        if self.other_tree is not None:
 
313
            self.other_tree.lock_read()
 
314
        try:
 
315
            merge = self.merge_type(pb=self._pb,
 
316
                                    change_reporter=self.change_reporter,
 
317
                                    **kwargs)
 
318
            if self.recurse == 'down':
 
319
                for path, file_id in self.this_tree.iter_references():
 
320
                    sub_tree = self.this_tree.get_nested_tree(file_id, path)
 
321
                    other_revision = self.other_tree.get_reference_revision(
 
322
                        file_id, path)
 
323
                    if  other_revision == sub_tree.last_revision():
 
324
                        continue
 
325
                    sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
 
326
                    sub_merge.merge_type = self.merge_type
 
327
                    relpath = self.this_tree.relpath(path)
 
328
                    other_branch = self.other_branch.reference_parent(file_id, relpath)
 
329
                    sub_merge.set_other_revision(other_revision, other_branch)
 
330
                    base_revision = self.base_tree.get_reference_revision(file_id)
 
331
                    sub_merge.base_tree = \
 
332
                        sub_tree.branch.repository.revision_tree(base_revision)
 
333
                    sub_merge.do_merge()
 
334
 
 
335
        finally:
 
336
            if self.other_tree is not None:
 
337
                self.other_tree.unlock()
 
338
            if self.base_tree is not None:
 
339
                self.base_tree.unlock()
 
340
            self.this_tree.unlock()
244
341
        if len(merge.cooked_conflicts) == 0:
245
342
            if not self.ignore_zero:
246
343
                note("All changes applied successfully.")
249
346
 
250
347
        return len(merge.cooked_conflicts)
251
348
 
252
 
    def regen_inventory(self, new_entries):
253
 
        old_entries = self.this_tree.read_working_inventory()
254
 
        new_inventory = {}
255
 
        by_path = {}
256
 
        new_entries_map = {} 
257
 
        for path, file_id in new_entries:
258
 
            if path is None:
259
 
                continue
260
 
            new_entries_map[file_id] = path
261
 
 
262
 
        def id2path(file_id):
263
 
            path = new_entries_map.get(file_id)
264
 
            if path is not None:
265
 
                return path
266
 
            entry = old_entries[file_id]
267
 
            if entry.parent_id is None:
268
 
                return entry.name
269
 
            return pathjoin(id2path(entry.parent_id), entry.name)
270
 
            
271
 
        for file_id in old_entries:
272
 
            entry = old_entries[file_id]
273
 
            path = id2path(file_id)
274
 
            new_inventory[file_id] = (path, file_id, entry.parent_id, 
275
 
                                      entry.kind)
276
 
            by_path[path] = file_id
277
 
        
278
 
        deletions = 0
279
 
        insertions = 0
280
 
        new_path_list = []
281
 
        for path, file_id in new_entries:
282
 
            if path is None:
283
 
                del new_inventory[file_id]
284
 
                deletions += 1
285
 
            else:
286
 
                new_path_list.append((path, file_id))
287
 
                if file_id not in old_entries:
288
 
                    insertions += 1
289
 
        # Ensure no file is added before its parent
290
 
        new_path_list.sort()
291
 
        for path, file_id in new_path_list:
292
 
            if path == '':
293
 
                parent = None
294
 
            else:
295
 
                parent = by_path[os.path.dirname(path)]
296
 
            abspath = pathjoin(self.this_tree.basedir, path)
297
 
            kind = bzrlib.osutils.file_kind(abspath)
298
 
            new_inventory[file_id] = (path, file_id, parent, kind)
299
 
            by_path[path] = file_id 
300
 
 
301
 
        # Get a list in insertion order
302
 
        new_inventory_list = new_inventory.values()
303
 
        mutter ("""Inventory regeneration:
304
 
    old length: %i insertions: %i deletions: %i new_length: %i"""\
305
 
            % (len(old_entries), insertions, deletions, 
306
 
               len(new_inventory_list)))
307
 
        assert len(new_inventory_list) == len(old_entries) + insertions\
308
 
            - deletions
309
 
        new_inventory_list.sort()
310
 
        return new_inventory_list
311
 
 
312
349
 
313
350
class Merge3Merger(object):
314
351
    """Three-way merger that uses the merge3 text merger"""
316
353
    supports_reprocess = True
317
354
    supports_show_base = True
318
355
    history_based = False
 
356
    winner_idx = {"this": 2, "other": 1, "conflict": 1}
319
357
 
320
358
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
321
 
                 reprocess=False, show_base=False, pb=DummyProgress()):
322
 
        """Initialize the merger object and perform the merge."""
 
359
                 interesting_ids=None, reprocess=False, show_base=False,
 
360
                 pb=DummyProgress(), pp=None, change_reporter=None,
 
361
                 interesting_files=None):
 
362
        """Initialize the merger object and perform the merge.
 
363
 
 
364
        :param working_tree: The working tree to apply the merge to
 
365
        :param this_tree: The local tree in the merge operation
 
366
        :param base_tree: The common tree in the merge operation
 
367
        :param other_tree: The other other tree to merge changes from
 
368
        :param interesting_ids: The file_ids of files that should be
 
369
            participate in the merge.  May not be combined with
 
370
            interesting_files.
 
371
        :param: reprocess If True, perform conflict-reduction processing.
 
372
        :param show_base: If True, show the base revision in text conflicts.
 
373
            (incompatible with reprocess)
 
374
        :param pb: A Progress bar
 
375
        :param pp: A ProgressPhase object
 
376
        :param change_reporter: An object that should report changes made
 
377
        :param interesting_files: The tree-relative paths of files that should
 
378
            participate in the merge.  If these paths refer to directories,
 
379
            the contents of those directories will also be included.  May not
 
380
            be combined with interesting_ids.  If neither interesting_files nor
 
381
            interesting_ids is specified, all files may participate in the
 
382
            merge.
 
383
        """
323
384
        object.__init__(self)
 
385
        if interesting_files is not None:
 
386
            assert interesting_ids is None
 
387
        self.interesting_ids = interesting_ids
 
388
        self.interesting_files = interesting_files
324
389
        self.this_tree = working_tree
 
390
        self.this_tree.lock_tree_write()
325
391
        self.base_tree = base_tree
 
392
        self.base_tree.lock_read()
326
393
        self.other_tree = other_tree
 
394
        self.other_tree.lock_read()
327
395
        self._raw_conflicts = []
328
396
        self.cooked_conflicts = []
329
397
        self.reprocess = reprocess
330
398
        self.show_base = show_base
331
399
        self.pb = pb
 
400
        self.pp = pp
 
401
        self.change_reporter = change_reporter
 
402
        if self.pp is None:
 
403
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
332
404
 
333
 
        all_ids = set(base_tree)
334
 
        all_ids.update(other_tree)
335
405
        self.tt = TreeTransform(working_tree, self.pb)
336
406
        try:
337
 
            for num, file_id in enumerate(all_ids):
338
 
                self.pb.update('Preparing file merge', num+1, len(all_ids))
339
 
                self.merge_names(file_id)
340
 
                file_status = self.merge_contents(file_id)
341
 
                self.merge_executable(file_id, file_status)
342
 
            self.pb.clear()
343
 
                
344
 
            fs_conflicts = resolve_conflicts(self.tt, self.pb)
 
407
            self.pp.next_phase()
 
408
            entries = self._entries3()
 
409
            child_pb = ui.ui_factory.nested_progress_bar()
 
410
            try:
 
411
                for num, (file_id, changed, parents3, names3,
 
412
                          executable3) in enumerate(entries):
 
413
                    child_pb.update('Preparing file merge', num, len(entries))
 
414
                    self._merge_names(file_id, parents3, names3)
 
415
                    if changed:
 
416
                        file_status = self.merge_contents(file_id)
 
417
                    else:
 
418
                        file_status = 'unmodified'
 
419
                    self._merge_executable(file_id,
 
420
                        executable3, file_status)
 
421
            finally:
 
422
                child_pb.finished()
 
423
            self.fix_root()
 
424
            self.pp.next_phase()
 
425
            child_pb = ui.ui_factory.nested_progress_bar()
 
426
            try:
 
427
                fs_conflicts = resolve_conflicts(self.tt, child_pb,
 
428
                    lambda t, c: conflict_pass(t, c, self.other_tree))
 
429
            finally:
 
430
                child_pb.finished()
 
431
            if change_reporter is not None:
 
432
                from bzrlib import delta
 
433
                delta.report_changes(self.tt._iter_changes(), change_reporter)
345
434
            self.cook_conflicts(fs_conflicts)
346
 
            for line in conflicts_strings(self.cooked_conflicts):
347
 
                warning(line)
348
 
            self.tt.apply()
349
 
        finally:
 
435
            for conflict in self.cooked_conflicts:
 
436
                warning(conflict)
 
437
            self.pp.next_phase()
 
438
            results = self.tt.apply(no_conflicts=True)
 
439
            self.write_modified(results)
350
440
            try:
351
 
                self.tt.finalize()
352
 
            except:
 
441
                working_tree.add_conflicts(self.cooked_conflicts)
 
442
            except UnsupportedOperation:
353
443
                pass
354
 
       
 
444
        finally:
 
445
            self.tt.finalize()
 
446
            self.other_tree.unlock()
 
447
            self.base_tree.unlock()
 
448
            self.this_tree.unlock()
 
449
            self.pb.clear()
 
450
 
 
451
    def _entries3(self):
 
452
        """Gather data about files modified between three trees.
 
453
 
 
454
        Return a list of tuples of file_id, changed, parents3, names3,
 
455
        executable3.  changed is a boolean indicating whether the file contents
 
456
        or kind were changed.  parents3 is a tuple of parent ids for base,
 
457
        other and this.  names3 is a tuple of names for base, other and this.
 
458
        executable3 is a tuple of execute-bit values for base, other and this.
 
459
        """
 
460
        result = []
 
461
        iterator = self.other_tree._iter_changes(self.base_tree,
 
462
                include_unchanged=True, specific_files=self.interesting_files,
 
463
                extra_trees=[self.this_tree])
 
464
        for (file_id, paths, changed, versioned, parents, names, kind,
 
465
             executable) in iterator:
 
466
            if (self.interesting_ids is not None and
 
467
                file_id not in self.interesting_ids):
 
468
                continue
 
469
            if file_id in self.this_tree.inventory:
 
470
                entry = self.this_tree.inventory[file_id]
 
471
                this_name = entry.name
 
472
                this_parent = entry.parent_id
 
473
                this_executable = entry.executable
 
474
            else:
 
475
                this_name = None
 
476
                this_parent = None
 
477
                this_executable = None
 
478
            parents3 = parents + (this_parent,)
 
479
            names3 = names + (this_name,)
 
480
            executable3 = executable + (this_executable,)
 
481
            result.append((file_id, changed, parents3, names3, executable3))
 
482
        return result
 
483
 
 
484
    def fix_root(self):
 
485
        try:
 
486
            self.tt.final_kind(self.tt.root)
 
487
        except NoSuchFile:
 
488
            self.tt.cancel_deletion(self.tt.root)
 
489
        if self.tt.final_file_id(self.tt.root) is None:
 
490
            self.tt.version_file(self.tt.tree_file_id(self.tt.root), 
 
491
                                 self.tt.root)
 
492
        if self.other_tree.inventory.root is None:
 
493
            return
 
494
        other_root_file_id = self.other_tree.inventory.root.file_id
 
495
        other_root = self.tt.trans_id_file_id(other_root_file_id)
 
496
        if other_root == self.tt.root:
 
497
            return
 
498
        try:
 
499
            self.tt.final_kind(other_root)
 
500
        except NoSuchFile:
 
501
            return
 
502
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
 
503
        self.tt.cancel_creation(other_root)
 
504
        self.tt.cancel_versioning(other_root)
 
505
 
 
506
    def reparent_children(self, ie, target):
 
507
        for thing, child in ie.children.iteritems():
 
508
            trans_id = self.tt.trans_id_file_id(child.file_id)
 
509
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
 
510
 
 
511
    def write_modified(self, results):
 
512
        modified_hashes = {}
 
513
        for path in results.modified_paths:
 
514
            file_id = self.this_tree.path2id(self.this_tree.relpath(path))
 
515
            if file_id is None:
 
516
                continue
 
517
            hash = self.this_tree.get_file_sha1(file_id)
 
518
            if hash is None:
 
519
                continue
 
520
            modified_hashes[file_id] = hash
 
521
        self.this_tree.set_merge_modified(modified_hashes)
 
522
 
355
523
    @staticmethod
356
524
    def parent(entry, file_id):
357
525
        """Determine the parent for a file_id (used as a key method)"""
390
558
        return tree.kind(file_id)
391
559
 
392
560
    @staticmethod
 
561
    def _three_way(base, other, this):
 
562
        #if base == other, either they all agree, or only THIS has changed.
 
563
        if base == other:
 
564
            return 'this'
 
565
        elif this not in (base, other):
 
566
            return 'conflict'
 
567
        # "Ambiguous clean merge" -- both sides have made the same change.
 
568
        elif this == other:
 
569
            return "this"
 
570
        # this == base: only other has changed.
 
571
        else:
 
572
            return "other"
 
573
 
 
574
    @staticmethod
393
575
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
394
576
        """Do a three-way test on a scalar.
395
577
        Return "this", "other" or "conflict", depending whether a value wins.
410
592
            return "other"
411
593
 
412
594
    def merge_names(self, file_id):
413
 
        """Perform a merge on file_id names and parents"""
414
595
        def get_entry(tree):
415
596
            if file_id in tree.inventory:
416
597
                return tree.inventory[file_id]
419
600
        this_entry = get_entry(self.this_tree)
420
601
        other_entry = get_entry(self.other_tree)
421
602
        base_entry = get_entry(self.base_tree)
422
 
        name_winner = self.scalar_three_way(this_entry, base_entry, 
423
 
                                            other_entry, file_id, self.name)
424
 
        parent_id_winner = self.scalar_three_way(this_entry, base_entry, 
425
 
                                                 other_entry, file_id, 
426
 
                                                 self.parent)
427
 
        if this_entry is None:
 
603
        entries = (base_entry, other_entry, this_entry)
 
604
        names = []
 
605
        parents = []
 
606
        for entry in entries:
 
607
            if entry is None:
 
608
                names.append(None)
 
609
                parents.append(None)
 
610
            else:
 
611
                names.append(entry.name)
 
612
                parents.append(entry.parent_id)
 
613
        return self._merge_names(file_id, parents, names)
 
614
 
 
615
    def _merge_names(self, file_id, parents, names):
 
616
        """Perform a merge on file_id names and parents"""
 
617
        base_name, other_name, this_name = names
 
618
        base_parent, other_parent, this_parent = parents
 
619
 
 
620
        name_winner = self._three_way(*names)
 
621
 
 
622
        parent_id_winner = self._three_way(*parents)
 
623
        if this_name is None:
428
624
            if name_winner == "this":
429
625
                name_winner = "other"
430
626
            if parent_id_winner == "this":
434
630
        if name_winner == "conflict":
435
631
            trans_id = self.tt.trans_id_file_id(file_id)
436
632
            self._raw_conflicts.append(('name conflict', trans_id, 
437
 
                                        self.name(this_entry, file_id), 
438
 
                                        self.name(other_entry, file_id)))
 
633
                                        this_name, other_name))
439
634
        if parent_id_winner == "conflict":
440
635
            trans_id = self.tt.trans_id_file_id(file_id)
441
636
            self._raw_conflicts.append(('parent conflict', trans_id, 
442
 
                                        self.parent(this_entry, file_id), 
443
 
                                        self.parent(other_entry, file_id)))
444
 
        if other_entry is None:
 
637
                                        this_parent, other_parent))
 
638
        if other_name is None:
445
639
            # it doesn't matter whether the result was 'other' or 
446
640
            # 'conflict'-- if there's no 'other', we leave it alone.
447
641
            return
448
642
        # if we get here, name_winner and parent_winner are set to safe values.
449
 
        winner_entry = {"this": this_entry, "other": other_entry, 
450
 
                        "conflict": other_entry}
451
643
        trans_id = self.tt.trans_id_file_id(file_id)
452
 
        parent_id = winner_entry[parent_id_winner].parent_id
453
 
        parent_trans_id = self.tt.trans_id_file_id(parent_id)
454
 
        self.tt.adjust_path(winner_entry[name_winner].name, parent_trans_id,
455
 
                            trans_id)
 
644
        parent_id = parents[self.winner_idx[parent_id_winner]]
 
645
        if parent_id is not None:
 
646
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
647
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
 
648
                                parent_trans_id, trans_id)
456
649
 
457
650
    def merge_contents(self, file_id):
458
651
        """Performa a merge on file_id contents."""
460
653
            if file_id not in tree:
461
654
                return (None, None)
462
655
            kind = tree.kind(file_id)
463
 
            if kind == "root_directory":
464
 
                kind = "directory"
465
656
            if kind == "file":
466
657
                contents = tree.get_file_sha1(file_id)
467
658
            elif kind == "symlink":
469
660
            else:
470
661
                contents = None
471
662
            return kind, contents
 
663
 
 
664
        def contents_conflict():
 
665
            trans_id = self.tt.trans_id_file_id(file_id)
 
666
            name = self.tt.final_name(trans_id)
 
667
            parent_id = self.tt.final_parent(trans_id)
 
668
            if file_id in self.this_tree.inventory:
 
669
                self.tt.unversion_file(trans_id)
 
670
                if file_id in self.this_tree:
 
671
                    self.tt.delete_contents(trans_id)
 
672
            file_group = self._dump_conflicts(name, parent_id, file_id, 
 
673
                                              set_version=True)
 
674
            self._raw_conflicts.append(('contents conflict', file_group))
 
675
 
472
676
        # See SPOT run.  run, SPOT, run.
473
677
        # So we're not QUITE repeating ourselves; we do tricky things with
474
678
        # file kind...
505
709
                # THIS and OTHER are both files, so text merge.  Either
506
710
                # BASE is a file, or both converted to files, so at least we
507
711
                # have agreement that output should be a file.
 
712
                try:
 
713
                    self.text_merge(file_id, trans_id)
 
714
                except BinaryFile:
 
715
                    return contents_conflict()
508
716
                if file_id not in self.this_tree.inventory:
509
717
                    self.tt.version_file(file_id, trans_id)
510
 
                self.text_merge(file_id, trans_id)
511
718
                try:
512
719
                    self.tt.tree_kind(trans_id)
513
720
                    self.tt.delete_contents(trans_id)
516
723
                return "modified"
517
724
            else:
518
725
                # Scalar conflict, can't text merge.  Dump conflicts
519
 
                trans_id = self.tt.trans_id_file_id(file_id)
520
 
                name = self.tt.final_name(trans_id)
521
 
                parent_id = self.tt.final_parent(trans_id)
522
 
                if file_id in self.this_tree.inventory:
523
 
                    self.tt.unversion_file(trans_id)
524
 
                    self.tt.delete_contents(trans_id)
525
 
                file_group = self._dump_conflicts(name, parent_id, file_id, 
526
 
                                                  set_version=True)
527
 
                self._raw_conflicts.append(('contents conflict', file_group))
 
726
                return contents_conflict()
528
727
 
529
728
    def get_lines(self, tree, file_id):
530
729
        """Return the lines in a file, or an empty list."""
611
810
 
612
811
    def merge_executable(self, file_id, file_status):
613
812
        """Perform a merge on the execute bit."""
 
813
        executable = [self.executable(t, file_id) for t in (self.base_tree,
 
814
                      self.other_tree, self.this_tree)]
 
815
        self._merge_executable(file_id, executable, file_status)
 
816
 
 
817
    def _merge_executable(self, file_id, executable, file_status):
 
818
        """Perform a merge on the execute bit."""
 
819
        base_executable, other_executable, this_executable = executable
614
820
        if file_status == "deleted":
615
821
            return
616
822
        trans_id = self.tt.trans_id_file_id(file_id)
619
825
                return
620
826
        except NoSuchFile:
621
827
            return
622
 
        winner = self.scalar_three_way(self.this_tree, self.base_tree, 
623
 
                                       self.other_tree, file_id, 
624
 
                                       self.executable)
 
828
        winner = self._three_way(*executable)
625
829
        if winner == "conflict":
626
830
        # There must be a None in here, if we have a conflict, but we
627
831
        # need executability since file status was not deleted.
628
 
            if self.other_tree.is_executable(file_id) is None:
 
832
            if self.executable(self.other_tree, file_id) is None:
629
833
                winner = "this"
630
834
            else:
631
835
                winner = "other"
632
836
        if winner == "this":
633
837
            if file_status == "modified":
634
 
                executability = self.this_tree.is_executable(file_id)
 
838
                executability = this_executable
635
839
                if executability is not None:
636
840
                    trans_id = self.tt.trans_id_file_id(file_id)
637
841
                    self.tt.set_executability(executability, trans_id)
638
842
        else:
639
843
            assert winner == "other"
640
844
            if file_id in self.other_tree:
641
 
                executability = self.other_tree.is_executable(file_id)
 
845
                executability = other_executable
642
846
            elif file_id in self.this_tree:
643
 
                executability = self.this_tree.is_executable(file_id)
 
847
                executability = this_executable
644
848
            elif file_id in self.base_tree:
645
 
                executability = self.base_tree.is_executable(file_id)
 
849
                executability = base_executable
646
850
            if executability is not None:
647
851
                trans_id = self.tt.trans_id_file_id(file_id)
648
852
                self.tt.set_executability(executability, trans_id)
649
853
 
650
854
    def cook_conflicts(self, fs_conflicts):
651
855
        """Convert all conflicts into a form that doesn't depend on trans_id"""
 
856
        from conflicts import Conflict
652
857
        name_conflicts = {}
653
858
        self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
654
859
        fp = FinalPaths(self.tt)
671
876
                    if path.endswith(suffix):
672
877
                        path = path[:-len(suffix)]
673
878
                        break
674
 
                self.cooked_conflicts.append((conflict_type, file_id, path))
 
879
                c = Conflict.factory(conflict_type, path=path, file_id=file_id)
 
880
                self.cooked_conflicts.append(c)
675
881
            if conflict_type == 'text conflict':
676
882
                trans_id = conflict[1]
677
883
                path = fp.get_path(trans_id)
678
884
                file_id = self.tt.final_file_id(trans_id)
679
 
                self.cooked_conflicts.append((conflict_type, file_id, path))
 
885
                c = Conflict.factory(conflict_type, path=path, file_id=file_id)
 
886
                self.cooked_conflicts.append(c)
680
887
 
681
888
        for trans_id, conflicts in name_conflicts.iteritems():
682
889
            try:
691
898
            except KeyError:
692
899
                this_name = other_name = self.tt.final_name(trans_id)
693
900
            other_path = fp.get_path(trans_id)
694
 
            if this_parent is not None:
 
901
            if this_parent is not None and this_name is not None:
695
902
                this_parent_path = \
696
903
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
697
904
                this_path = pathjoin(this_parent_path, this_name)
698
905
            else:
699
906
                this_path = "<deleted>"
700
907
            file_id = self.tt.final_file_id(trans_id)
701
 
            self.cooked_conflicts.append(('path conflict', file_id, this_path, 
702
 
                                         other_path))
 
908
            c = Conflict.factory('path conflict', path=this_path,
 
909
                                 conflict_path=other_path, file_id=file_id)
 
910
            self.cooked_conflicts.append(c)
 
911
        self.cooked_conflicts.sort(key=Conflict.sort_key)
703
912
 
704
913
 
705
914
class WeaveMerger(Merge3Merger):
706
915
    """Three-way tree merger, text weave merger."""
707
 
    supports_reprocess = False
 
916
    supports_reprocess = True
708
917
    supports_show_base = False
709
918
 
710
919
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
711
 
                 pb=DummyProgress()):
712
 
        self.this_revision_tree = self._get_revision_tree(this_tree)
713
 
        self.other_revision_tree = self._get_revision_tree(other_tree)
 
920
                 interesting_ids=None, pb=DummyProgress(), pp=None,
 
921
                 reprocess=False, change_reporter=None,
 
922
                 interesting_files=None):
714
923
        super(WeaveMerger, self).__init__(working_tree, this_tree, 
715
 
                                          base_tree, other_tree, pb=pb)
716
 
 
717
 
    def _get_revision_tree(self, tree):
718
 
        """Return a revision tree releated to this tree.
719
 
        If the tree is a WorkingTree, the basis will be returned.
720
 
        """
721
 
        if getattr(tree, 'get_weave', False) is False:
722
 
            # If we have a WorkingTree, try using the basis
723
 
            return tree.branch.basis_tree()
724
 
        else:
725
 
            return tree
726
 
 
727
 
    def _check_file(self, file_id):
728
 
        """Check that the revision tree's version of the file matches."""
729
 
        for tree, rt in ((self.this_tree, self.this_revision_tree), 
730
 
                         (self.other_tree, self.other_revision_tree)):
731
 
            if rt is tree:
732
 
                continue
733
 
            if tree.get_file_sha1(file_id) != rt.get_file_sha1(file_id):
734
 
                raise WorkingTreeNotRevision(self.this_tree)
 
924
                                          base_tree, other_tree, 
 
925
                                          interesting_ids=interesting_ids, 
 
926
                                          pb=pb, pp=pp, reprocess=reprocess,
 
927
                                          change_reporter=change_reporter)
735
928
 
736
929
    def _merged_lines(self, file_id):
737
930
        """Generate the merged lines.
738
931
        There is no distinction between lines that are meant to contain <<<<<<<
739
932
        and conflicts.
740
933
        """
741
 
        weave = self.this_revision_tree.get_weave(file_id)
742
 
        this_revision_id = self.this_revision_tree.inventory[file_id].revision
743
 
        other_revision_id = \
744
 
            self.other_revision_tree.inventory[file_id].revision
745
 
        this_i = weave.lookup(this_revision_id)
746
 
        other_i = weave.lookup(other_revision_id)
747
 
        plan =  weave.plan_merge(this_i, other_i)
748
 
        return weave.weave_merge(plan)
 
934
        plan = self.this_tree.plan_file_merge(file_id, self.other_tree)
 
935
        textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
 
936
            '>>>>>>> MERGE-SOURCE\n')
 
937
        return textmerge.merge_lines(self.reprocess)
749
938
 
750
939
    def text_merge(self, file_id, trans_id):
751
940
        """Perform a (weave) text merge for a given file and file-id.
752
941
        If conflicts are encountered, .THIS and .OTHER files will be emitted,
753
942
        and a conflict will be noted.
754
943
        """
755
 
        self._check_file(file_id)
756
 
        lines = self._merged_lines(file_id)
757
 
        conflicts = '<<<<<<<\n' in lines
 
944
        lines, conflicts = self._merged_lines(file_id)
 
945
        lines = list(lines)
 
946
        # Note we're checking whether the OUTPUT is binary in this case, 
 
947
        # because we don't want to get into weave merge guts.
 
948
        check_text_lines(lines)
758
949
        self.tt.create_file(lines, trans_id)
759
950
        if conflicts:
760
951
            self._raw_conflicts.append(('text conflict', trans_id))
767
958
 
768
959
class Diff3Merger(Merge3Merger):
769
960
    """Three-way merger using external diff3 for text merging"""
 
961
 
770
962
    def dump_file(self, temp_dir, name, tree, file_id):
771
963
        out_path = pathjoin(temp_dir, name)
772
 
        out_file = file(out_path, "wb")
773
 
        in_file = tree.get_file(file_id)
774
 
        for line in in_file:
775
 
            out_file.write(line)
 
964
        out_file = open(out_path, "wb")
 
965
        try:
 
966
            in_file = tree.get_file(file_id)
 
967
            for line in in_file:
 
968
                out_file.write(line)
 
969
        finally:
 
970
            out_file.close()
776
971
        return out_path
777
972
 
778
973
    def text_merge(self, file_id, trans_id):
781
976
        will be dumped, and a will be conflict noted.
782
977
        """
783
978
        import bzrlib.patch
784
 
        temp_dir = mkdtemp(prefix="bzr-")
 
979
        temp_dir = osutils.mkdtemp(prefix="bzr-")
785
980
        try:
786
981
            new_file = pathjoin(temp_dir, "new")
787
982
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
790
985
            status = bzrlib.patch.diff3(new_file, this, base, other)
791
986
            if status not in (0, 1):
792
987
                raise BzrError("Unhandled diff3 exit code")
793
 
            self.tt.create_file(file(new_file, "rb"), trans_id)
 
988
            f = open(new_file, 'rb')
 
989
            try:
 
990
                self.tt.create_file(f, trans_id)
 
991
            finally:
 
992
                f.close()
794
993
            if status == 1:
795
994
                name = self.tt.final_name(trans_id)
796
995
                parent_id = self.tt.final_parent(trans_id)
797
996
                self._dump_conflicts(name, parent_id, file_id)
798
 
            self._raw_conflicts.append(('text conflict', trans_id))
 
997
                self._raw_conflicts.append(('text conflict', trans_id))
799
998
        finally:
800
 
            rmtree(temp_dir)
 
999
            osutils.rmtree(temp_dir)
801
1000
 
802
1001
 
803
1002
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
804
 
                backup_files=False, 
805
 
                merge_type=Merge3Merger, 
806
 
                interesting_ids=None, 
807
 
                show_base=False, 
808
 
                reprocess=False, 
 
1003
                backup_files=False,
 
1004
                merge_type=Merge3Merger,
 
1005
                interesting_ids=None,
 
1006
                show_base=False,
 
1007
                reprocess=False,
809
1008
                other_rev_id=None,
810
1009
                interesting_files=None,
811
1010
                this_tree=None,
812
 
                pb=DummyProgress()):
 
1011
                pb=DummyProgress(),
 
1012
                change_reporter=None):
813
1013
    """Primary interface for merging. 
814
1014
 
815
1015
        typical use is probably 
817
1017
                     branch.get_revision_tree(base_revision))'
818
1018
        """
819
1019
    if this_tree is None:
820
 
        warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
821
 
             "bzrlib version 0.8.",
822
 
             DeprecationWarning,
823
 
             stacklevel=2)
824
 
        this_tree = this_branch.bzrdir.open_workingtree()
825
 
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree, 
826
 
                    pb=pb)
 
1020
        raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
 
1021
            "parameter as of bzrlib version 0.8.")
 
1022
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
 
1023
                    pb=pb, change_reporter=change_reporter)
827
1024
    merger.backup_files = backup_files
828
1025
    merger.merge_type = merge_type
829
1026
    merger.interesting_ids = interesting_ids
 
1027
    merger.ignore_zero = ignore_zero
830
1028
    if interesting_files:
831
1029
        assert not interesting_ids, ('Only supply interesting_ids'
832
1030
                                     ' or interesting_files')
833
 
        merger._set_interesting_files(interesting_files)
834
 
    merger.show_base = show_base 
 
1031
        merger.interesting_files = interesting_files
 
1032
    merger.show_base = show_base
835
1033
    merger.reprocess = reprocess
836
1034
    merger.other_rev_id = other_rev_id
837
1035
    merger.other_basis = other_rev_id
838
1036
    return merger.do_merge()
839
1037
 
840
 
 
841
 
merge_types = {     "merge3": (Merge3Merger, "Native diff3-style merge"), 
842
 
                     "diff3": (Diff3Merger,  "Merge using external diff3"),
843
 
                     'weave': (WeaveMerger, "Weave-based merge")
844
 
              }
 
1038
def get_merge_type_registry():
 
1039
    """Merge type registry is in bzrlib.option to avoid circular imports.
 
1040
 
 
1041
    This method provides a sanctioned way to retrieve it.
 
1042
    """
 
1043
    from bzrlib import option
 
1044
    return option._merge_type_registry
 
1045
 
 
1046
 
 
1047
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
 
1048
    def status_a(revision, text):
 
1049
        if revision in ancestors_b:
 
1050
            return 'killed-b', text
 
1051
        else:
 
1052
            return 'new-a', text
 
1053
 
 
1054
    def status_b(revision, text):
 
1055
        if revision in ancestors_a:
 
1056
            return 'killed-a', text
 
1057
        else:
 
1058
            return 'new-b', text
 
1059
 
 
1060
    plain_a = [t for (a, t) in annotated_a]
 
1061
    plain_b = [t for (a, t) in annotated_b]
 
1062
    matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
 
1063
    blocks = matcher.get_matching_blocks()
 
1064
    a_cur = 0
 
1065
    b_cur = 0
 
1066
    for ai, bi, l in blocks:
 
1067
        # process all mismatched sections
 
1068
        # (last mismatched section is handled because blocks always
 
1069
        # includes a 0-length last block)
 
1070
        for revision, text in annotated_a[a_cur:ai]:
 
1071
            yield status_a(revision, text)
 
1072
        for revision, text in annotated_b[b_cur:bi]:
 
1073
            yield status_b(revision, text)
 
1074
 
 
1075
        # and now the matched section
 
1076
        a_cur = ai + l
 
1077
        b_cur = bi + l
 
1078
        for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
 
1079
            assert text_a == text_b
 
1080
            yield "unchanged", text_a