~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-09-20 02:40:52 UTC
  • mfrom: (2835.1.1 ianc-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20070920024052-y2l7r5o00zrpnr73
No longer propagate index differences automatically (Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
 
 
 
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 tempfile import mkdtemp
 
20
import warnings
21
21
 
22
 
import bzrlib
 
22
from bzrlib import (
 
23
    errors,
 
24
    osutils,
 
25
    patiencediff,
 
26
    registry,
 
27
    revision as _mod_revision,
 
28
    )
23
29
from bzrlib.branch import Branch
24
30
from bzrlib.conflicts import ConflictList, Conflict
25
 
from bzrlib.delta import compare_trees
26
31
from bzrlib.errors import (BzrCommandError,
27
32
                           BzrError,
28
33
                           NoCommonAncestor,
37
42
                           BinaryFile,
38
43
                           )
39
44
from bzrlib.merge3 import Merge3
40
 
import bzrlib.osutils
41
 
from bzrlib.osutils import rename, pathjoin, rmtree
 
45
from bzrlib.osutils import rename, pathjoin
42
46
from progress import DummyProgress, ProgressPhase
43
 
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
44
 
from bzrlib.symbol_versioning import *
 
47
from bzrlib.revision import (is_ancestor, NULL_REVISION, ensure_null)
45
48
from bzrlib.textfile import check_text_lines
46
49
from bzrlib.trace import mutter, warning, note
47
50
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
48
 
                              FinalPaths, create_by_entry, unique_add)
49
 
from bzrlib.versionedfile import WeaveMerge
50
 
import bzrlib.ui
 
51
                              conflict_pass, FinalPaths, create_by_entry,
 
52
                              unique_add, ROOT_PARENT)
 
53
from bzrlib.versionedfile import PlanWeaveMerge
 
54
from bzrlib import ui
51
55
 
52
56
# TODO: Report back as changes are merged in
53
57
 
54
 
def _get_tree(treespec, local_branch=None):
55
 
    location, revno = treespec
56
 
    branch = Branch.open_containing(location)[0]
57
 
    if revno is None:
58
 
        revision = None
59
 
    elif revno == -1:
60
 
        revision = branch.last_revision()
61
 
    else:
62
 
        revision = branch.get_rev_id(revno)
63
 
        if revision is None:
64
 
            revision = NULL_REVISION
65
 
    return branch, _get_revid_tree(branch, revision, local_branch)
66
 
 
67
 
 
68
 
def _get_revid_tree(branch, revision, local_branch):
69
 
    if revision is None:
70
 
        base_tree = branch.bzrdir.open_workingtree()
71
 
    else:
72
 
        if local_branch is not None:
73
 
            if local_branch.base != branch.base:
74
 
                local_branch.fetch(branch, revision)
75
 
            base_tree = local_branch.repository.revision_tree(revision)
76
 
        else:
77
 
            base_tree = branch.repository.revision_tree(revision)
78
 
    return base_tree
79
 
 
80
58
 
81
59
def transform_tree(from_tree, to_tree, interesting_ids=None):
82
60
    merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
84
62
 
85
63
 
86
64
class Merger(object):
87
 
    def __init__(self, this_branch, other_tree=None, base_tree=None, 
88
 
                 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'):
89
68
        object.__init__(self)
90
69
        assert this_tree is not None, "this_tree is required"
91
70
        self.this_branch = this_branch
92
 
        self.this_basis = this_branch.last_revision()
 
71
        self.this_basis = _mod_revision.ensure_null(
 
72
            this_branch.last_revision())
93
73
        self.this_rev_id = None
94
74
        self.this_tree = this_tree
95
75
        self.this_revision_tree = None
96
76
        self.this_basis_tree = None
97
77
        self.other_tree = other_tree
 
78
        self.other_branch = None
98
79
        self.base_tree = base_tree
99
80
        self.ignore_zero = False
100
81
        self.backup_files = False
101
82
        self.interesting_ids = None
 
83
        self.interesting_files = None
102
84
        self.show_base = False
103
85
        self.reprocess = False
104
 
        self._pb = pb 
 
86
        self._pb = pb
105
87
        self.pp = None
106
 
 
107
 
 
108
 
    def revision_tree(self, revision_id):
109
 
        return self.this_branch.repository.revision_tree(revision_id)
 
88
        self.recurse = recurse
 
89
        self.change_reporter = change_reporter
 
90
        self._cached_trees = {}
 
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
 
 
152
    def revision_tree(self, revision_id, branch=None):
 
153
        if revision_id not in self._cached_trees:
 
154
            if branch is None:
 
155
                branch = self.this_branch
 
156
            try:
 
157
                tree = self.this_tree.revision_tree(revision_id)
 
158
            except errors.NoSuchRevisionInTree:
 
159
                tree = branch.repository.revision_tree(revision_id)
 
160
            self._cached_trees[revision_id] = tree
 
161
        return self._cached_trees[revision_id]
 
162
 
 
163
    def _get_tree(self, treespec, possible_transports=None):
 
164
        from bzrlib import workingtree
 
165
        location, revno = treespec
 
166
        if revno is None:
 
167
            tree = workingtree.WorkingTree.open_containing(location)[0]
 
168
            return tree.branch, tree
 
169
        branch = Branch.open_containing(location, possible_transports)[0]
 
170
        if revno == -1:
 
171
            revision_id = branch.last_revision()
 
172
        else:
 
173
            revision_id = branch.get_rev_id(revno)
 
174
        revision_id = ensure_null(revision_id)
 
175
        return branch, self.revision_tree(revision_id, branch)
110
176
 
111
177
    def ensure_revision_trees(self):
112
178
        if self.this_revision_tree is None:
113
 
            self.this_basis_tree = self.this_branch.repository.revision_tree(
114
 
                self.this_basis)
 
179
            self.this_basis_tree = self.revision_tree(self.this_basis)
115
180
            if self.this_basis == self.this_rev_id:
116
181
                self.this_revision_tree = self.this_basis_tree
117
182
 
118
183
        if self.other_rev_id is None:
119
184
            other_basis_tree = self.revision_tree(self.other_basis)
120
 
            changes = compare_trees(self.other_tree, other_basis_tree)
 
185
            changes = other_basis_tree.changes_from(self.other_tree)
121
186
            if changes.has_changed():
122
187
                raise WorkingTreeNotRevision(self.this_tree)
123
 
            other_rev_id = other_basis
 
188
            other_rev_id = self.other_basis
124
189
            self.other_tree = other_basis_tree
125
190
 
126
191
    def file_revisions(self, file_id):
137
202
        trees = (self.this_basis_tree, self.other_tree)
138
203
        return [get_id(tree, file_id) for tree in trees]
139
204
 
140
 
    def check_basis(self, check_clean):
141
 
        if self.this_basis is None:
142
 
            raise BzrCommandError("This branch has no commits")
 
205
    def check_basis(self, check_clean, require_commits=True):
 
206
        if self.this_basis is None and require_commits is True:
 
207
            raise BzrCommandError("This branch has no commits."
 
208
                                  " (perhaps you would prefer 'bzr pull')")
143
209
        if check_clean:
144
210
            self.compare_basis()
145
211
            if self.this_basis != self.this_rev_id:
146
 
                raise BzrCommandError("Working tree has uncommitted changes.")
 
212
                raise errors.UncommittedChanges(self.this_tree)
147
213
 
148
214
    def compare_basis(self):
149
 
        changes = compare_trees(self.this_tree, 
150
 
                                self.this_tree.basis_tree(), False)
 
215
        try:
 
216
            basis_tree = self.revision_tree(self.this_tree.last_revision())
 
217
        except errors.RevisionNotPresent:
 
218
            basis_tree = self.this_tree.basis_tree()
 
219
        changes = self.this_tree.changes_from(basis_tree)
151
220
        if not changes.has_changed():
152
221
            self.this_rev_id = self.this_basis
153
222
 
154
223
    def set_interesting_files(self, file_list):
155
 
        try:
156
 
            self._set_interesting_files(file_list)
157
 
        except NotVersionedError, e:
158
 
            raise BzrCommandError("%s is not a source file in any"
159
 
                                      " tree." % e.path)
160
 
 
161
 
    def _set_interesting_files(self, file_list):
162
 
        """Set the list of interesting ids from a list of files."""
163
 
        if file_list is None:
164
 
            self.interesting_ids = None
165
 
            return
166
 
 
167
 
        interesting_ids = set()
168
 
        for path in file_list:
169
 
            found_id = False
170
 
            for tree in (self.this_tree, self.base_tree, self.other_tree):
171
 
                file_id = tree.inventory.path2id(path)
172
 
                if file_id is not None:
173
 
                    interesting_ids.add(file_id)
174
 
                    found_id = True
175
 
            if not found_id:
176
 
                raise NotVersionedError(path=path)
177
 
        self.interesting_ids = interesting_ids
 
224
        self.interesting_files = file_list
178
225
 
179
226
    def set_pending(self):
180
 
        if not self.base_is_ancestor:
181
 
            return
182
 
        if self.other_rev_id is None:
183
 
            return
184
 
        ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
185
 
        if self.other_rev_id in ancestry:
186
 
            return
187
 
        self.this_tree.add_pending_merge(self.other_rev_id)
188
 
 
189
 
    def set_other(self, other_revision):
190
 
        other_branch, self.other_tree = _get_tree(other_revision, 
191
 
                                                  self.this_branch)
 
227
        if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
 
228
            return
 
229
        self._add_parent()
 
230
 
 
231
    def _add_parent(self):
 
232
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
 
233
        new_parent_trees = []
 
234
        for revision_id in new_parents:
 
235
            try:
 
236
                tree = self.revision_tree(revision_id)
 
237
            except errors.RevisionNotPresent:
 
238
                tree = None
 
239
            else:
 
240
                tree.lock_read()
 
241
            new_parent_trees.append((revision_id, tree))
 
242
        try:
 
243
            self.this_tree.set_parent_trees(new_parent_trees,
 
244
                                            allow_leftmost_as_ghost=True)
 
245
        finally:
 
246
            for _revision_id, tree in new_parent_trees:
 
247
                if tree is not None:
 
248
                    tree.unlock()
 
249
 
 
250
    def set_other(self, other_revision, possible_transports=None):
 
251
        """Set the revision and tree to merge from.
 
252
 
 
253
        This sets the other_tree, other_rev_id, other_basis attributes.
 
254
 
 
255
        :param other_revision: The [path, revision] list to merge from.
 
256
        """
 
257
        self.other_branch, self.other_tree = self._get_tree(other_revision,
 
258
                                                            possible_transports)
192
259
        if other_revision[1] == -1:
193
 
            self.other_rev_id = other_branch.last_revision()
194
 
            if self.other_rev_id is None:
195
 
                raise NoCommits(other_branch)
 
260
            self.other_rev_id = _mod_revision.ensure_null(
 
261
                self.other_branch.last_revision())
 
262
            if _mod_revision.is_null(self.other_rev_id):
 
263
                raise NoCommits(self.other_branch)
196
264
            self.other_basis = self.other_rev_id
197
265
        elif other_revision[1] is not None:
198
 
            self.other_rev_id = other_branch.get_rev_id(other_revision[1])
 
266
            self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
199
267
            self.other_basis = self.other_rev_id
200
268
        else:
201
269
            self.other_rev_id = None
202
 
            self.other_basis = other_branch.last_revision()
 
270
            self.other_basis = self.other_branch.last_revision()
203
271
            if self.other_basis is None:
204
 
                raise NoCommits(other_branch)
205
 
        if other_branch.base != self.this_branch.base:
206
 
            self.this_branch.fetch(other_branch, last_revision=self.other_basis)
 
272
                raise NoCommits(self.other_branch)
 
273
        if self.other_rev_id is not None:
 
274
            self._cached_trees[self.other_rev_id] = self.other_tree
 
275
        self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
 
276
 
 
277
    def set_other_revision(self, revision_id, other_branch):
 
278
        """Set 'other' based on a branch and revision id
 
279
 
 
280
        :param revision_id: The revision to use for a tree
 
281
        :param other_branch: The branch containing this tree
 
282
        """
 
283
        self.other_rev_id = revision_id
 
284
        self.other_branch = other_branch
 
285
        self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
 
286
        self.other_tree = self.revision_tree(revision_id)
 
287
        self.other_basis = revision_id
 
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
 
 
306
    def _maybe_fetch(self, source, target, revision_id):
 
307
        if not source.repository.has_same_location(target.repository):
 
308
            target.fetch(source, revision_id)
 
309
 
 
310
    def find_base(self):
 
311
        this_repo = self.this_branch.repository
 
312
        graph = this_repo.get_graph()
 
313
        revisions = [ensure_null(self.this_basis),
 
314
                     ensure_null(self.other_basis)]
 
315
        if NULL_REVISION in revisions:
 
316
            self.base_rev_id = NULL_REVISION
 
317
        else:
 
318
            self.base_rev_id = graph.find_unique_lca(*revisions)
 
319
            if self.base_rev_id == NULL_REVISION:
 
320
                raise UnrelatedBranches()
 
321
        self.base_tree = self.revision_tree(self.base_rev_id)
 
322
        self.base_is_ancestor = True
 
323
        self.base_is_other_ancestor = True
207
324
 
208
325
    def set_base(self, base_revision):
 
326
        """Set the base revision to use for the merge.
 
327
 
 
328
        :param base_revision: A 2-list containing a path and revision number.
 
329
        """
209
330
        mutter("doing merge() with no base_revision specified")
210
331
        if base_revision == [None, None]:
211
 
            try:
212
 
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
213
 
                try:
214
 
                    this_repo = self.this_branch.repository
215
 
                    self.base_rev_id = common_ancestor(self.this_basis, 
216
 
                                                       self.other_basis, 
217
 
                                                       this_repo, pb)
218
 
                finally:
219
 
                    pb.finished()
220
 
            except NoCommonAncestor:
221
 
                raise UnrelatedBranches()
222
 
            self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
223
 
                                            None)
224
 
            self.base_is_ancestor = True
 
332
            self.find_base()
225
333
        else:
226
 
            base_branch, self.base_tree = _get_tree(base_revision)
 
334
            base_branch, self.base_tree = self._get_tree(base_revision)
227
335
            if base_revision[1] == -1:
228
336
                self.base_rev_id = base_branch.last_revision()
229
337
            elif base_revision[1] is None:
230
 
                self.base_rev_id = None
 
338
                self.base_rev_id = _mod_revision.NULL_REVISION
231
339
            else:
232
 
                self.base_rev_id = base_branch.get_rev_id(base_revision[1])
233
 
            if self.this_branch.base != base_branch.base:
234
 
                self.this_branch.fetch(base_branch)
 
340
                self.base_rev_id = _mod_revision.ensure_null(
 
341
                    base_branch.get_rev_id(base_revision[1]))
 
342
            self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
235
343
            self.base_is_ancestor = is_ancestor(self.this_basis, 
236
344
                                                self.base_rev_id,
237
345
                                                self.this_branch)
 
346
            self.base_is_other_ancestor = is_ancestor(self.other_basis,
 
347
                                                      self.base_rev_id,
 
348
                                                      self.this_branch)
238
349
 
239
350
    def do_merge(self):
240
 
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree, 
241
 
                  'other_tree': self.other_tree, 
 
351
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
 
352
                  'other_tree': self.other_tree,
242
353
                  'interesting_ids': self.interesting_ids,
 
354
                  'interesting_files': self.interesting_files,
243
355
                  'pp': self.pp}
244
356
        if self.merge_type.requires_base:
245
357
            kwargs['base_tree'] = self.base_tree
253
365
        elif self.show_base:
254
366
            raise BzrError("Showing base is not supported for this"
255
367
                                  " merge type. %s" % self.merge_type)
256
 
        merge = self.merge_type(pb=self._pb, **kwargs)
 
368
        self.this_tree.lock_tree_write()
 
369
        if self.base_tree is not None:
 
370
            self.base_tree.lock_read()
 
371
        if self.other_tree is not None:
 
372
            self.other_tree.lock_read()
 
373
        try:
 
374
            merge = self.merge_type(pb=self._pb,
 
375
                                    change_reporter=self.change_reporter,
 
376
                                    **kwargs)
 
377
            if self.recurse == 'down':
 
378
                for path, file_id in self.this_tree.iter_references():
 
379
                    sub_tree = self.this_tree.get_nested_tree(file_id, path)
 
380
                    other_revision = self.other_tree.get_reference_revision(
 
381
                        file_id, path)
 
382
                    if  other_revision == sub_tree.last_revision():
 
383
                        continue
 
384
                    sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
 
385
                    sub_merge.merge_type = self.merge_type
 
386
                    relpath = self.this_tree.relpath(path)
 
387
                    other_branch = self.other_branch.reference_parent(file_id, relpath)
 
388
                    sub_merge.set_other_revision(other_revision, other_branch)
 
389
                    base_revision = self.base_tree.get_reference_revision(file_id)
 
390
                    sub_merge.base_tree = \
 
391
                        sub_tree.branch.repository.revision_tree(base_revision)
 
392
                    sub_merge.do_merge()
 
393
 
 
394
        finally:
 
395
            if self.other_tree is not None:
 
396
                self.other_tree.unlock()
 
397
            if self.base_tree is not None:
 
398
                self.base_tree.unlock()
 
399
            self.this_tree.unlock()
257
400
        if len(merge.cooked_conflicts) == 0:
258
401
            if not self.ignore_zero:
259
402
                note("All changes applied successfully.")
262
405
 
263
406
        return len(merge.cooked_conflicts)
264
407
 
265
 
    def regen_inventory(self, new_entries):
266
 
        old_entries = self.this_tree.read_working_inventory()
267
 
        new_inventory = {}
268
 
        by_path = {}
269
 
        new_entries_map = {} 
270
 
        for path, file_id in new_entries:
271
 
            if path is None:
272
 
                continue
273
 
            new_entries_map[file_id] = path
274
 
 
275
 
        def id2path(file_id):
276
 
            path = new_entries_map.get(file_id)
277
 
            if path is not None:
278
 
                return path
279
 
            entry = old_entries[file_id]
280
 
            if entry.parent_id is None:
281
 
                return entry.name
282
 
            return pathjoin(id2path(entry.parent_id), entry.name)
283
 
            
284
 
        for file_id in old_entries:
285
 
            entry = old_entries[file_id]
286
 
            path = id2path(file_id)
287
 
            if file_id in self.base_tree.inventory:
288
 
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
289
 
            else:
290
 
                executable = getattr(entry, 'executable', False)
291
 
            new_inventory[file_id] = (path, file_id, entry.parent_id, 
292
 
                                      entry.kind, executable)
293
 
                                      
294
 
            by_path[path] = file_id
295
 
        
296
 
        deletions = 0
297
 
        insertions = 0
298
 
        new_path_list = []
299
 
        for path, file_id in new_entries:
300
 
            if path is None:
301
 
                del new_inventory[file_id]
302
 
                deletions += 1
303
 
            else:
304
 
                new_path_list.append((path, file_id))
305
 
                if file_id not in old_entries:
306
 
                    insertions += 1
307
 
        # Ensure no file is added before its parent
308
 
        new_path_list.sort()
309
 
        for path, file_id in new_path_list:
310
 
            if path == '':
311
 
                parent = None
312
 
            else:
313
 
                parent = by_path[os.path.dirname(path)]
314
 
            abspath = pathjoin(self.this_tree.basedir, path)
315
 
            kind = bzrlib.osutils.file_kind(abspath)
316
 
            if file_id in self.base_tree.inventory:
317
 
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
318
 
            else:
319
 
                executable = False
320
 
            new_inventory[file_id] = (path, file_id, parent, kind, executable)
321
 
            by_path[path] = file_id 
322
 
 
323
 
        # Get a list in insertion order
324
 
        new_inventory_list = new_inventory.values()
325
 
        mutter ("""Inventory regeneration:
326
 
    old length: %i insertions: %i deletions: %i new_length: %i"""\
327
 
            % (len(old_entries), insertions, deletions, 
328
 
               len(new_inventory_list)))
329
 
        assert len(new_inventory_list) == len(old_entries) + insertions\
330
 
            - deletions
331
 
        new_inventory_list.sort()
332
 
        return new_inventory_list
333
 
 
334
408
 
335
409
class Merge3Merger(object):
336
410
    """Three-way merger that uses the merge3 text merger"""
338
412
    supports_reprocess = True
339
413
    supports_show_base = True
340
414
    history_based = False
 
415
    winner_idx = {"this": 2, "other": 1, "conflict": 1}
341
416
 
342
417
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
343
418
                 interesting_ids=None, reprocess=False, show_base=False,
344
 
                 pb=DummyProgress(), pp=None):
345
 
        """Initialize the merger object and perform the merge."""
 
419
                 pb=DummyProgress(), pp=None, change_reporter=None,
 
420
                 interesting_files=None):
 
421
        """Initialize the merger object and perform the merge.
 
422
 
 
423
        :param working_tree: The working tree to apply the merge to
 
424
        :param this_tree: The local tree in the merge operation
 
425
        :param base_tree: The common tree in the merge operation
 
426
        :param other_tree: The other other tree to merge changes from
 
427
        :param interesting_ids: The file_ids of files that should be
 
428
            participate in the merge.  May not be combined with
 
429
            interesting_files.
 
430
        :param: reprocess If True, perform conflict-reduction processing.
 
431
        :param show_base: If True, show the base revision in text conflicts.
 
432
            (incompatible with reprocess)
 
433
        :param pb: A Progress bar
 
434
        :param pp: A ProgressPhase object
 
435
        :param change_reporter: An object that should report changes made
 
436
        :param interesting_files: The tree-relative paths of files that should
 
437
            participate in the merge.  If these paths refer to directories,
 
438
            the contents of those directories will also be included.  May not
 
439
            be combined with interesting_ids.  If neither interesting_files nor
 
440
            interesting_ids is specified, all files may participate in the
 
441
            merge.
 
442
        """
346
443
        object.__init__(self)
 
444
        if interesting_files is not None:
 
445
            assert interesting_ids is None
 
446
        self.interesting_ids = interesting_ids
 
447
        self.interesting_files = interesting_files
347
448
        self.this_tree = working_tree
 
449
        self.this_tree.lock_tree_write()
348
450
        self.base_tree = base_tree
 
451
        self.base_tree.lock_read()
349
452
        self.other_tree = other_tree
 
453
        self.other_tree.lock_read()
350
454
        self._raw_conflicts = []
351
455
        self.cooked_conflicts = []
352
456
        self.reprocess = reprocess
353
457
        self.show_base = show_base
354
458
        self.pb = pb
355
459
        self.pp = pp
 
460
        self.change_reporter = change_reporter
356
461
        if self.pp is None:
357
462
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
358
463
 
359
 
        if interesting_ids is not None:
360
 
            all_ids = interesting_ids
361
 
        else:
362
 
            all_ids = set(base_tree)
363
 
            all_ids.update(other_tree)
364
 
        working_tree.lock_write()
365
464
        self.tt = TreeTransform(working_tree, self.pb)
366
465
        try:
367
466
            self.pp.next_phase()
368
 
            child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
467
            entries = self._entries3()
 
468
            child_pb = ui.ui_factory.nested_progress_bar()
369
469
            try:
370
 
                for num, file_id in enumerate(all_ids):
371
 
                    child_pb.update('Preparing file merge', num, len(all_ids))
372
 
                    self.merge_names(file_id)
373
 
                    file_status = self.merge_contents(file_id)
374
 
                    self.merge_executable(file_id, file_status)
 
470
                for num, (file_id, changed, parents3, names3,
 
471
                          executable3) in enumerate(entries):
 
472
                    child_pb.update('Preparing file merge', num, len(entries))
 
473
                    self._merge_names(file_id, parents3, names3)
 
474
                    if changed:
 
475
                        file_status = self.merge_contents(file_id)
 
476
                    else:
 
477
                        file_status = 'unmodified'
 
478
                    self._merge_executable(file_id,
 
479
                        executable3, file_status)
375
480
            finally:
376
481
                child_pb.finished()
377
 
                
 
482
            self.fix_root()
378
483
            self.pp.next_phase()
379
 
            child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
484
            child_pb = ui.ui_factory.nested_progress_bar()
380
485
            try:
381
 
                fs_conflicts = resolve_conflicts(self.tt, child_pb)
 
486
                fs_conflicts = resolve_conflicts(self.tt, child_pb,
 
487
                    lambda t, c: conflict_pass(t, c, self.other_tree))
382
488
            finally:
383
489
                child_pb.finished()
 
490
            if change_reporter is not None:
 
491
                from bzrlib import delta
 
492
                delta.report_changes(self.tt._iter_changes(), change_reporter)
384
493
            self.cook_conflicts(fs_conflicts)
385
494
            for conflict in self.cooked_conflicts:
386
495
                warning(conflict)
387
496
            self.pp.next_phase()
388
 
            results = self.tt.apply()
 
497
            results = self.tt.apply(no_conflicts=True)
389
498
            self.write_modified(results)
390
499
            try:
391
 
                working_tree.set_conflicts(ConflictList(self.cooked_conflicts))
 
500
                working_tree.add_conflicts(self.cooked_conflicts)
392
501
            except UnsupportedOperation:
393
502
                pass
394
503
        finally:
395
 
            try:
396
 
                self.tt.finalize()
397
 
            except:
398
 
                pass
399
 
            working_tree.unlock()
 
504
            self.tt.finalize()
 
505
            self.other_tree.unlock()
 
506
            self.base_tree.unlock()
 
507
            self.this_tree.unlock()
400
508
            self.pb.clear()
401
509
 
 
510
    def _entries3(self):
 
511
        """Gather data about files modified between three trees.
 
512
 
 
513
        Return a list of tuples of file_id, changed, parents3, names3,
 
514
        executable3.  changed is a boolean indicating whether the file contents
 
515
        or kind were changed.  parents3 is a tuple of parent ids for base,
 
516
        other and this.  names3 is a tuple of names for base, other and this.
 
517
        executable3 is a tuple of execute-bit values for base, other and this.
 
518
        """
 
519
        result = []
 
520
        iterator = self.other_tree._iter_changes(self.base_tree,
 
521
                include_unchanged=True, specific_files=self.interesting_files,
 
522
                extra_trees=[self.this_tree])
 
523
        for (file_id, paths, changed, versioned, parents, names, kind,
 
524
             executable) in iterator:
 
525
            if (self.interesting_ids is not None and
 
526
                file_id not in self.interesting_ids):
 
527
                continue
 
528
            if file_id in self.this_tree.inventory:
 
529
                entry = self.this_tree.inventory[file_id]
 
530
                this_name = entry.name
 
531
                this_parent = entry.parent_id
 
532
                this_executable = entry.executable
 
533
            else:
 
534
                this_name = None
 
535
                this_parent = None
 
536
                this_executable = None
 
537
            parents3 = parents + (this_parent,)
 
538
            names3 = names + (this_name,)
 
539
            executable3 = executable + (this_executable,)
 
540
            result.append((file_id, changed, parents3, names3, executable3))
 
541
        return result
 
542
 
 
543
    def fix_root(self):
 
544
        try:
 
545
            self.tt.final_kind(self.tt.root)
 
546
        except NoSuchFile:
 
547
            self.tt.cancel_deletion(self.tt.root)
 
548
        if self.tt.final_file_id(self.tt.root) is None:
 
549
            self.tt.version_file(self.tt.tree_file_id(self.tt.root), 
 
550
                                 self.tt.root)
 
551
        if self.other_tree.inventory.root is None:
 
552
            return
 
553
        other_root_file_id = self.other_tree.inventory.root.file_id
 
554
        other_root = self.tt.trans_id_file_id(other_root_file_id)
 
555
        if other_root == self.tt.root:
 
556
            return
 
557
        try:
 
558
            self.tt.final_kind(other_root)
 
559
        except NoSuchFile:
 
560
            return
 
561
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
 
562
        self.tt.cancel_creation(other_root)
 
563
        self.tt.cancel_versioning(other_root)
 
564
 
 
565
    def reparent_children(self, ie, target):
 
566
        for thing, child in ie.children.iteritems():
 
567
            trans_id = self.tt.trans_id_file_id(child.file_id)
 
568
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
 
569
 
402
570
    def write_modified(self, results):
403
571
        modified_hashes = {}
404
572
        for path in results.modified_paths:
449
617
        return tree.kind(file_id)
450
618
 
451
619
    @staticmethod
 
620
    def _three_way(base, other, this):
 
621
        #if base == other, either they all agree, or only THIS has changed.
 
622
        if base == other:
 
623
            return 'this'
 
624
        elif this not in (base, other):
 
625
            return 'conflict'
 
626
        # "Ambiguous clean merge" -- both sides have made the same change.
 
627
        elif this == other:
 
628
            return "this"
 
629
        # this == base: only other has changed.
 
630
        else:
 
631
            return "other"
 
632
 
 
633
    @staticmethod
452
634
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
453
635
        """Do a three-way test on a scalar.
454
636
        Return "this", "other" or "conflict", depending whether a value wins.
469
651
            return "other"
470
652
 
471
653
    def merge_names(self, file_id):
472
 
        """Perform a merge on file_id names and parents"""
473
654
        def get_entry(tree):
474
655
            if file_id in tree.inventory:
475
656
                return tree.inventory[file_id]
478
659
        this_entry = get_entry(self.this_tree)
479
660
        other_entry = get_entry(self.other_tree)
480
661
        base_entry = get_entry(self.base_tree)
481
 
        name_winner = self.scalar_three_way(this_entry, base_entry, 
482
 
                                            other_entry, file_id, self.name)
483
 
        parent_id_winner = self.scalar_three_way(this_entry, base_entry, 
484
 
                                                 other_entry, file_id, 
485
 
                                                 self.parent)
486
 
        if this_entry is None:
 
662
        entries = (base_entry, other_entry, this_entry)
 
663
        names = []
 
664
        parents = []
 
665
        for entry in entries:
 
666
            if entry is None:
 
667
                names.append(None)
 
668
                parents.append(None)
 
669
            else:
 
670
                names.append(entry.name)
 
671
                parents.append(entry.parent_id)
 
672
        return self._merge_names(file_id, parents, names)
 
673
 
 
674
    def _merge_names(self, file_id, parents, names):
 
675
        """Perform a merge on file_id names and parents"""
 
676
        base_name, other_name, this_name = names
 
677
        base_parent, other_parent, this_parent = parents
 
678
 
 
679
        name_winner = self._three_way(*names)
 
680
 
 
681
        parent_id_winner = self._three_way(*parents)
 
682
        if this_name is None:
487
683
            if name_winner == "this":
488
684
                name_winner = "other"
489
685
            if parent_id_winner == "this":
493
689
        if name_winner == "conflict":
494
690
            trans_id = self.tt.trans_id_file_id(file_id)
495
691
            self._raw_conflicts.append(('name conflict', trans_id, 
496
 
                                        self.name(this_entry, file_id), 
497
 
                                        self.name(other_entry, file_id)))
 
692
                                        this_name, other_name))
498
693
        if parent_id_winner == "conflict":
499
694
            trans_id = self.tt.trans_id_file_id(file_id)
500
695
            self._raw_conflicts.append(('parent conflict', trans_id, 
501
 
                                        self.parent(this_entry, file_id), 
502
 
                                        self.parent(other_entry, file_id)))
503
 
        if other_entry is None:
 
696
                                        this_parent, other_parent))
 
697
        if other_name is None:
504
698
            # it doesn't matter whether the result was 'other' or 
505
699
            # 'conflict'-- if there's no 'other', we leave it alone.
506
700
            return
507
701
        # if we get here, name_winner and parent_winner are set to safe values.
508
 
        winner_entry = {"this": this_entry, "other": other_entry, 
509
 
                        "conflict": other_entry}
510
702
        trans_id = self.tt.trans_id_file_id(file_id)
511
 
        parent_id = winner_entry[parent_id_winner].parent_id
512
 
        parent_trans_id = self.tt.trans_id_file_id(parent_id)
513
 
        self.tt.adjust_path(winner_entry[name_winner].name, parent_trans_id,
514
 
                            trans_id)
 
703
        parent_id = parents[self.winner_idx[parent_id_winner]]
 
704
        if parent_id is not None:
 
705
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
706
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
 
707
                                parent_trans_id, trans_id)
515
708
 
516
709
    def merge_contents(self, file_id):
517
710
        """Performa a merge on file_id contents."""
519
712
            if file_id not in tree:
520
713
                return (None, None)
521
714
            kind = tree.kind(file_id)
522
 
            if kind == "root_directory":
523
 
                kind = "directory"
524
715
            if kind == "file":
525
716
                contents = tree.get_file_sha1(file_id)
526
717
            elif kind == "symlink":
535
726
            parent_id = self.tt.final_parent(trans_id)
536
727
            if file_id in self.this_tree.inventory:
537
728
                self.tt.unversion_file(trans_id)
538
 
                self.tt.delete_contents(trans_id)
 
729
                if file_id in self.this_tree:
 
730
                    self.tt.delete_contents(trans_id)
539
731
            file_group = self._dump_conflicts(name, parent_id, file_id, 
540
732
                                              set_version=True)
541
733
            self._raw_conflicts.append(('contents conflict', file_group))
677
869
 
678
870
    def merge_executable(self, file_id, file_status):
679
871
        """Perform a merge on the execute bit."""
 
872
        executable = [self.executable(t, file_id) for t in (self.base_tree,
 
873
                      self.other_tree, self.this_tree)]
 
874
        self._merge_executable(file_id, executable, file_status)
 
875
 
 
876
    def _merge_executable(self, file_id, executable, file_status):
 
877
        """Perform a merge on the execute bit."""
 
878
        base_executable, other_executable, this_executable = executable
680
879
        if file_status == "deleted":
681
880
            return
682
881
        trans_id = self.tt.trans_id_file_id(file_id)
685
884
                return
686
885
        except NoSuchFile:
687
886
            return
688
 
        winner = self.scalar_three_way(self.this_tree, self.base_tree, 
689
 
                                       self.other_tree, file_id, 
690
 
                                       self.executable)
 
887
        winner = self._three_way(*executable)
691
888
        if winner == "conflict":
692
889
        # There must be a None in here, if we have a conflict, but we
693
890
        # need executability since file status was not deleted.
694
 
            if self.other_tree.is_executable(file_id) is None:
 
891
            if self.executable(self.other_tree, file_id) is None:
695
892
                winner = "this"
696
893
            else:
697
894
                winner = "other"
698
895
        if winner == "this":
699
896
            if file_status == "modified":
700
 
                executability = self.this_tree.is_executable(file_id)
 
897
                executability = this_executable
701
898
                if executability is not None:
702
899
                    trans_id = self.tt.trans_id_file_id(file_id)
703
900
                    self.tt.set_executability(executability, trans_id)
704
901
        else:
705
902
            assert winner == "other"
706
903
            if file_id in self.other_tree:
707
 
                executability = self.other_tree.is_executable(file_id)
 
904
                executability = other_executable
708
905
            elif file_id in self.this_tree:
709
 
                executability = self.this_tree.is_executable(file_id)
 
906
                executability = this_executable
710
907
            elif file_id in self.base_tree:
711
 
                executability = self.base_tree.is_executable(file_id)
 
908
                executability = base_executable
712
909
            if executability is not None:
713
910
                trans_id = self.tt.trans_id_file_id(file_id)
714
911
                self.tt.set_executability(executability, trans_id)
760
957
            except KeyError:
761
958
                this_name = other_name = self.tt.final_name(trans_id)
762
959
            other_path = fp.get_path(trans_id)
763
 
            if this_parent is not None:
 
960
            if this_parent is not None and this_name is not None:
764
961
                this_parent_path = \
765
962
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
766
963
                this_path = pathjoin(this_parent_path, this_name)
780
977
 
781
978
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
782
979
                 interesting_ids=None, pb=DummyProgress(), pp=None,
783
 
                 reprocess=False):
784
 
        self.this_revision_tree = self._get_revision_tree(this_tree)
785
 
        self.other_revision_tree = self._get_revision_tree(other_tree)
 
980
                 reprocess=False, change_reporter=None,
 
981
                 interesting_files=None):
786
982
        super(WeaveMerger, self).__init__(working_tree, this_tree, 
787
983
                                          base_tree, other_tree, 
788
984
                                          interesting_ids=interesting_ids, 
789
 
                                          pb=pb, pp=pp, reprocess=reprocess)
790
 
 
791
 
    def _get_revision_tree(self, tree):
792
 
        """Return a revision tree releated to this tree.
793
 
        If the tree is a WorkingTree, the basis will be returned.
794
 
        """
795
 
        if getattr(tree, 'get_weave', False) is False:
796
 
            # If we have a WorkingTree, try using the basis
797
 
            return tree.branch.basis_tree()
798
 
        else:
799
 
            return tree
800
 
 
801
 
    def _check_file(self, file_id):
802
 
        """Check that the revision tree's version of the file matches."""
803
 
        for tree, rt in ((self.this_tree, self.this_revision_tree), 
804
 
                         (self.other_tree, self.other_revision_tree)):
805
 
            if rt is tree:
806
 
                continue
807
 
            if tree.get_file_sha1(file_id) != rt.get_file_sha1(file_id):
808
 
                raise WorkingTreeNotRevision(self.this_tree)
 
985
                                          pb=pb, pp=pp, reprocess=reprocess,
 
986
                                          change_reporter=change_reporter)
809
987
 
810
988
    def _merged_lines(self, file_id):
811
989
        """Generate the merged lines.
812
990
        There is no distinction between lines that are meant to contain <<<<<<<
813
991
        and conflicts.
814
992
        """
815
 
        weave = self.this_revision_tree.get_weave(file_id)
816
 
        this_revision_id = self.this_revision_tree.inventory[file_id].revision
817
 
        other_revision_id = \
818
 
            self.other_revision_tree.inventory[file_id].revision
819
 
        wm = WeaveMerge(weave, this_revision_id, other_revision_id, 
820
 
                        '<<<<<<< TREE\n', '>>>>>>> MERGE-SOURCE\n')
821
 
        return wm.merge_lines(self.reprocess)
 
993
        plan = self.this_tree.plan_file_merge(file_id, self.other_tree)
 
994
        textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
 
995
            '>>>>>>> MERGE-SOURCE\n')
 
996
        return textmerge.merge_lines(self.reprocess)
822
997
 
823
998
    def text_merge(self, file_id, trans_id):
824
999
        """Perform a (weave) text merge for a given file and file-id.
825
1000
        If conflicts are encountered, .THIS and .OTHER files will be emitted,
826
1001
        and a conflict will be noted.
827
1002
        """
828
 
        self._check_file(file_id)
829
1003
        lines, conflicts = self._merged_lines(file_id)
830
1004
        lines = list(lines)
831
1005
        # Note we're checking whether the OUTPUT is binary in this case, 
843
1017
 
844
1018
class Diff3Merger(Merge3Merger):
845
1019
    """Three-way merger using external diff3 for text merging"""
 
1020
 
846
1021
    def dump_file(self, temp_dir, name, tree, file_id):
847
1022
        out_path = pathjoin(temp_dir, name)
848
 
        out_file = file(out_path, "wb")
849
 
        in_file = tree.get_file(file_id)
850
 
        for line in in_file:
851
 
            out_file.write(line)
 
1023
        out_file = open(out_path, "wb")
 
1024
        try:
 
1025
            in_file = tree.get_file(file_id)
 
1026
            for line in in_file:
 
1027
                out_file.write(line)
 
1028
        finally:
 
1029
            out_file.close()
852
1030
        return out_path
853
1031
 
854
1032
    def text_merge(self, file_id, trans_id):
857
1035
        will be dumped, and a will be conflict noted.
858
1036
        """
859
1037
        import bzrlib.patch
860
 
        temp_dir = mkdtemp(prefix="bzr-")
 
1038
        temp_dir = osutils.mkdtemp(prefix="bzr-")
861
1039
        try:
862
1040
            new_file = pathjoin(temp_dir, "new")
863
1041
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
866
1044
            status = bzrlib.patch.diff3(new_file, this, base, other)
867
1045
            if status not in (0, 1):
868
1046
                raise BzrError("Unhandled diff3 exit code")
869
 
            self.tt.create_file(file(new_file, "rb"), trans_id)
 
1047
            f = open(new_file, 'rb')
 
1048
            try:
 
1049
                self.tt.create_file(f, trans_id)
 
1050
            finally:
 
1051
                f.close()
870
1052
            if status == 1:
871
1053
                name = self.tt.final_name(trans_id)
872
1054
                parent_id = self.tt.final_parent(trans_id)
873
1055
                self._dump_conflicts(name, parent_id, file_id)
874
 
            self._raw_conflicts.append(('text conflict', trans_id))
 
1056
                self._raw_conflicts.append(('text conflict', trans_id))
875
1057
        finally:
876
 
            rmtree(temp_dir)
 
1058
            osutils.rmtree(temp_dir)
877
1059
 
878
1060
 
879
1061
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
880
 
                backup_files=False, 
881
 
                merge_type=Merge3Merger, 
882
 
                interesting_ids=None, 
883
 
                show_base=False, 
884
 
                reprocess=False, 
 
1062
                backup_files=False,
 
1063
                merge_type=Merge3Merger,
 
1064
                interesting_ids=None,
 
1065
                show_base=False,
 
1066
                reprocess=False,
885
1067
                other_rev_id=None,
886
1068
                interesting_files=None,
887
1069
                this_tree=None,
888
 
                pb=DummyProgress()):
 
1070
                pb=DummyProgress(),
 
1071
                change_reporter=None):
889
1072
    """Primary interface for merging. 
890
1073
 
891
1074
        typical use is probably 
893
1076
                     branch.get_revision_tree(base_revision))'
894
1077
        """
895
1078
    if this_tree is None:
896
 
        warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
897
 
             "bzrlib version 0.8.",
898
 
             DeprecationWarning,
899
 
             stacklevel=2)
900
 
        this_tree = this_branch.bzrdir.open_workingtree()
901
 
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree, 
902
 
                    pb=pb)
 
1079
        raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
 
1080
            "parameter as of bzrlib version 0.8.")
 
1081
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
 
1082
                    pb=pb, change_reporter=change_reporter)
903
1083
    merger.backup_files = backup_files
904
1084
    merger.merge_type = merge_type
905
1085
    merger.interesting_ids = interesting_ids
907
1087
    if interesting_files:
908
1088
        assert not interesting_ids, ('Only supply interesting_ids'
909
1089
                                     ' or interesting_files')
910
 
        merger._set_interesting_files(interesting_files)
911
 
    merger.show_base = show_base 
 
1090
        merger.interesting_files = interesting_files
 
1091
    merger.show_base = show_base
912
1092
    merger.reprocess = reprocess
913
1093
    merger.other_rev_id = other_rev_id
914
1094
    merger.other_basis = other_rev_id
915
1095
    return merger.do_merge()
916
1096
 
917
 
 
918
 
merge_types = {     "merge3": (Merge3Merger, "Native diff3-style merge"), 
919
 
                     "diff3": (Diff3Merger,  "Merge using external diff3"),
920
 
                     'weave': (WeaveMerger, "Weave-based merge")
921
 
              }
922
 
 
923
 
 
924
 
def merge_type_help():
925
 
    templ = '%s%%7s: %%s' % (' '*12)
926
 
    lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
927
 
    return '\n'.join(lines)
 
1097
def get_merge_type_registry():
 
1098
    """Merge type registry is in bzrlib.option to avoid circular imports.
 
1099
 
 
1100
    This method provides a sanctioned way to retrieve it.
 
1101
    """
 
1102
    from bzrlib import option
 
1103
    return option._merge_type_registry
 
1104
 
 
1105
 
 
1106
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
 
1107
    def status_a(revision, text):
 
1108
        if revision in ancestors_b:
 
1109
            return 'killed-b', text
 
1110
        else:
 
1111
            return 'new-a', text
 
1112
 
 
1113
    def status_b(revision, text):
 
1114
        if revision in ancestors_a:
 
1115
            return 'killed-a', text
 
1116
        else:
 
1117
            return 'new-b', text
 
1118
 
 
1119
    plain_a = [t for (a, t) in annotated_a]
 
1120
    plain_b = [t for (a, t) in annotated_b]
 
1121
    matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
 
1122
    blocks = matcher.get_matching_blocks()
 
1123
    a_cur = 0
 
1124
    b_cur = 0
 
1125
    for ai, bi, l in blocks:
 
1126
        # process all mismatched sections
 
1127
        # (last mismatched section is handled because blocks always
 
1128
        # includes a 0-length last block)
 
1129
        for revision, text in annotated_a[a_cur:ai]:
 
1130
            yield status_a(revision, text)
 
1131
        for revision, text in annotated_b[b_cur:bi]:
 
1132
            yield status_b(revision, text)
 
1133
 
 
1134
        # and now the matched section
 
1135
        a_cur = ai + l
 
1136
        b_cur = bi + l
 
1137
        for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
 
1138
            assert text_a == text_b
 
1139
            yield "unchanged", text_a