~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Ian Clatworthy
  • Date: 2007-06-06 14:06:14 UTC
  • mto: This revision was merged to the branch mainline in revision 2520.
  • Revision ID: ian.clatworthy@internode.on.net-20070606140614-yp66v5i1gm5kruqp
Fixes #115491 - 'branch lp:projname' now creates ./projname as exected

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
    osutils,
 
24
    registry,
 
25
    )
23
26
from bzrlib.branch import Branch
24
27
from bzrlib.conflicts import ConflictList, Conflict
25
 
from bzrlib.delta import compare_trees
26
28
from bzrlib.errors import (BzrCommandError,
27
29
                           BzrError,
28
30
                           NoCommonAncestor,
37
39
                           BinaryFile,
38
40
                           )
39
41
from bzrlib.merge3 import Merge3
40
 
import bzrlib.osutils
41
 
from bzrlib.osutils import rename, pathjoin, rmtree
 
42
from bzrlib.osutils import rename, pathjoin
42
43
from progress import DummyProgress, ProgressPhase
43
44
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
44
 
from bzrlib.symbol_versioning import *
45
45
from bzrlib.textfile import check_text_lines
46
46
from bzrlib.trace import mutter, warning, note
47
47
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
48
 
                              FinalPaths, create_by_entry, unique_add)
 
48
                              FinalPaths, create_by_entry, unique_add,
 
49
                              ROOT_PARENT)
49
50
from bzrlib.versionedfile import WeaveMerge
50
 
import bzrlib.ui
 
51
from bzrlib import ui
51
52
 
52
53
# TODO: Report back as changes are merged in
53
54
 
54
55
def _get_tree(treespec, local_branch=None):
 
56
    from bzrlib import workingtree
55
57
    location, revno = treespec
 
58
    if revno is None:
 
59
        tree = workingtree.WorkingTree.open_containing(location)[0]
 
60
        return tree.branch, tree
56
61
    branch = Branch.open_containing(location)[0]
57
 
    if revno is None:
58
 
        revision = None
59
 
    elif revno == -1:
60
 
        revision = branch.last_revision()
 
62
    if revno == -1:
 
63
        revision_id = branch.last_revision()
61
64
    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:
 
65
        revision_id = branch.get_rev_id(revno)
 
66
    if revision_id is None:
 
67
        revision_id = NULL_REVISION
 
68
    return branch, _get_revid_tree(branch, revision_id, local_branch)
 
69
 
 
70
 
 
71
def _get_revid_tree(branch, revision_id, local_branch):
 
72
    if revision_id is None:
70
73
        base_tree = branch.bzrdir.open_workingtree()
71
74
    else:
72
75
        if local_branch is not None:
73
76
            if local_branch.base != branch.base:
74
 
                local_branch.fetch(branch, revision)
75
 
            base_tree = local_branch.repository.revision_tree(revision)
 
77
                local_branch.fetch(branch, revision_id)
 
78
            base_tree = local_branch.repository.revision_tree(revision_id)
76
79
        else:
77
 
            base_tree = branch.repository.revision_tree(revision)
 
80
            base_tree = branch.repository.revision_tree(revision_id)
78
81
    return base_tree
79
82
 
80
83
 
 
84
def _get_revid_tree_from_tree(tree, revision_id, local_branch):
 
85
    if revision_id is None:
 
86
        return tree
 
87
    if local_branch is not None:
 
88
        if local_branch.base != tree.branch.base:
 
89
            local_branch.fetch(tree.branch, revision_id)
 
90
        return local_branch.repository.revision_tree(revision_id)
 
91
    return tree.branch.repository.revision_tree(revision_id)
 
92
 
 
93
 
81
94
def transform_tree(from_tree, to_tree, interesting_ids=None):
82
95
    merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
83
96
                interesting_ids=interesting_ids, this_tree=from_tree)
84
97
 
85
98
 
86
99
class Merger(object):
87
 
    def __init__(self, this_branch, other_tree=None, base_tree=None, 
88
 
                 this_tree=None, pb=DummyProgress()):
 
100
    def __init__(self, this_branch, other_tree=None, base_tree=None,
 
101
                 this_tree=None, pb=DummyProgress(), change_reporter=None,
 
102
                 recurse='down'):
89
103
        object.__init__(self)
90
104
        assert this_tree is not None, "this_tree is required"
91
105
        self.this_branch = this_branch
95
109
        self.this_revision_tree = None
96
110
        self.this_basis_tree = None
97
111
        self.other_tree = other_tree
 
112
        self.other_branch = None
98
113
        self.base_tree = base_tree
99
114
        self.ignore_zero = False
100
115
        self.backup_files = False
101
116
        self.interesting_ids = None
102
117
        self.show_base = False
103
118
        self.reprocess = False
104
 
        self._pb = pb 
 
119
        self._pb = pb
105
120
        self.pp = None
106
 
 
 
121
        self.recurse = recurse
 
122
        self.change_reporter = change_reporter
107
123
 
108
124
    def revision_tree(self, revision_id):
109
125
        return self.this_branch.repository.revision_tree(revision_id)
117
133
 
118
134
        if self.other_rev_id is None:
119
135
            other_basis_tree = self.revision_tree(self.other_basis)
120
 
            changes = compare_trees(self.other_tree, other_basis_tree)
 
136
            changes = other_basis_tree.changes_from(self.other_tree)
121
137
            if changes.has_changed():
122
138
                raise WorkingTreeNotRevision(self.this_tree)
123
 
            other_rev_id = other_basis
 
139
            other_rev_id = self.other_basis
124
140
            self.other_tree = other_basis_tree
125
141
 
126
142
    def file_revisions(self, file_id):
137
153
        trees = (self.this_basis_tree, self.other_tree)
138
154
        return [get_id(tree, file_id) for tree in trees]
139
155
 
140
 
    def check_basis(self, check_clean):
141
 
        if self.this_basis is None:
142
 
            raise BzrCommandError("This branch has no commits")
 
156
    def check_basis(self, check_clean, require_commits=True):
 
157
        if self.this_basis is None and require_commits is True:
 
158
            raise BzrCommandError("This branch has no commits."
 
159
                                  " (perhaps you would prefer 'bzr pull')")
143
160
        if check_clean:
144
161
            self.compare_basis()
145
162
            if self.this_basis != self.this_rev_id:
146
163
                raise BzrCommandError("Working tree has uncommitted changes.")
147
164
 
148
165
    def compare_basis(self):
149
 
        changes = compare_trees(self.this_tree, 
150
 
                                self.this_tree.basis_tree(), False)
 
166
        changes = self.this_tree.changes_from(self.this_tree.basis_tree())
151
167
        if not changes.has_changed():
152
168
            self.this_rev_id = self.this_basis
153
169
 
167
183
        interesting_ids = set()
168
184
        for path in file_list:
169
185
            found_id = False
 
186
            # TODO: jam 20070226 The trees are not locked at this time,
 
187
            #       wouldn't it make merge faster if it locks everything in the
 
188
            #       beginning? It locks at do_merge time, but this happens
 
189
            #       before that.
170
190
            for tree in (self.this_tree, self.base_tree, self.other_tree):
171
 
                file_id = tree.inventory.path2id(path)
 
191
                file_id = tree.path2id(path)
172
192
                if file_id is not None:
173
193
                    interesting_ids.add(file_id)
174
194
                    found_id = True
184
204
        ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
185
205
        if self.other_rev_id in ancestry:
186
206
            return
187
 
        self.this_tree.add_pending_merge(self.other_rev_id)
 
207
        self.this_tree.add_parent_tree((self.other_rev_id, self.other_tree))
188
208
 
189
209
    def set_other(self, other_revision):
190
 
        other_branch, self.other_tree = _get_tree(other_revision, 
 
210
        """Set the revision and tree to merge from.
 
211
 
 
212
        This sets the other_tree, other_rev_id, other_basis attributes.
 
213
 
 
214
        :param other_revision: The [path, revision] list to merge from.
 
215
        """
 
216
        self.other_branch, self.other_tree = _get_tree(other_revision,
191
217
                                                  self.this_branch)
192
218
        if other_revision[1] == -1:
193
 
            self.other_rev_id = other_branch.last_revision()
 
219
            self.other_rev_id = self.other_branch.last_revision()
194
220
            if self.other_rev_id is None:
195
 
                raise NoCommits(other_branch)
 
221
                raise NoCommits(self.other_branch)
196
222
            self.other_basis = self.other_rev_id
197
223
        elif other_revision[1] is not None:
198
 
            self.other_rev_id = other_branch.get_rev_id(other_revision[1])
 
224
            self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
199
225
            self.other_basis = self.other_rev_id
200
226
        else:
201
227
            self.other_rev_id = None
202
 
            self.other_basis = other_branch.last_revision()
 
228
            self.other_basis = self.other_branch.last_revision()
203
229
            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)
 
230
                raise NoCommits(self.other_branch)
 
231
        if self.other_branch.base != self.this_branch.base:
 
232
            self.this_branch.fetch(self.other_branch,
 
233
                                   last_revision=self.other_basis)
 
234
 
 
235
    def set_other_revision(self, revision_id, other_branch):
 
236
        """Set 'other' based on a branch and revision id
 
237
 
 
238
        :param revision_id: The revision to use for a tree
 
239
        :param other_branch: The branch containing this tree
 
240
        """
 
241
        self.other_rev_id = revision_id
 
242
        self.other_branch = other_branch
 
243
        self.this_branch.fetch(other_branch, self.other_rev_id)
 
244
        self.other_tree = self.revision_tree(revision_id)
 
245
        self.other_basis = revision_id
 
246
 
 
247
    def find_base(self):
 
248
        self.set_base([None, None])
207
249
 
208
250
    def set_base(self, base_revision):
 
251
        """Set the base revision to use for the merge.
 
252
 
 
253
        :param base_revision: A 2-list containing a path and revision number.
 
254
        """
209
255
        mutter("doing merge() with no base_revision specified")
210
256
        if base_revision == [None, None]:
211
257
            try:
212
 
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
258
                pb = ui.ui_factory.nested_progress_bar()
213
259
                try:
214
260
                    this_repo = self.this_branch.repository
215
261
                    self.base_rev_id = common_ancestor(self.this_basis, 
219
265
                    pb.finished()
220
266
            except NoCommonAncestor:
221
267
                raise UnrelatedBranches()
222
 
            self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
223
 
                                            None)
 
268
            self.base_tree = _get_revid_tree_from_tree(self.this_tree,
 
269
                                                       self.base_rev_id,
 
270
                                                       None)
224
271
            self.base_is_ancestor = True
225
272
        else:
226
273
            base_branch, self.base_tree = _get_tree(base_revision)
237
284
                                                self.this_branch)
238
285
 
239
286
    def do_merge(self):
240
 
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree, 
241
 
                  'other_tree': self.other_tree, 
 
287
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
 
288
                  'other_tree': self.other_tree,
242
289
                  'interesting_ids': self.interesting_ids,
243
290
                  'pp': self.pp}
244
291
        if self.merge_type.requires_base:
253
300
        elif self.show_base:
254
301
            raise BzrError("Showing base is not supported for this"
255
302
                                  " merge type. %s" % self.merge_type)
256
 
        merge = self.merge_type(pb=self._pb, **kwargs)
 
303
        self.this_tree.lock_tree_write()
 
304
        if self.base_tree is not None:
 
305
            self.base_tree.lock_read()
 
306
        if self.other_tree is not None:
 
307
            self.other_tree.lock_read()
 
308
        try:
 
309
            merge = self.merge_type(pb=self._pb,
 
310
                                    change_reporter=self.change_reporter,
 
311
                                    **kwargs)
 
312
            if self.recurse == 'down':
 
313
                for path, file_id in self.this_tree.iter_references():
 
314
                    sub_tree = self.this_tree.get_nested_tree(file_id, path)
 
315
                    other_revision = self.other_tree.get_reference_revision(
 
316
                        file_id, path)
 
317
                    if  other_revision == sub_tree.last_revision():
 
318
                        continue
 
319
                    sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
 
320
                    sub_merge.merge_type = self.merge_type
 
321
                    relpath = self.this_tree.relpath(path)
 
322
                    other_branch = self.other_branch.reference_parent(file_id, relpath)
 
323
                    sub_merge.set_other_revision(other_revision, other_branch)
 
324
                    base_revision = self.base_tree.get_reference_revision(file_id)
 
325
                    sub_merge.base_tree = \
 
326
                        sub_tree.branch.repository.revision_tree(base_revision)
 
327
                    sub_merge.do_merge()
 
328
 
 
329
        finally:
 
330
            if self.other_tree is not None:
 
331
                self.other_tree.unlock()
 
332
            if self.base_tree is not None:
 
333
                self.base_tree.unlock()
 
334
            self.this_tree.unlock()
257
335
        if len(merge.cooked_conflicts) == 0:
258
336
            if not self.ignore_zero:
259
337
                note("All changes applied successfully.")
312
390
            else:
313
391
                parent = by_path[os.path.dirname(path)]
314
392
            abspath = pathjoin(self.this_tree.basedir, path)
315
 
            kind = bzrlib.osutils.file_kind(abspath)
 
393
            kind = osutils.file_kind(abspath)
316
394
            if file_id in self.base_tree.inventory:
317
395
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
318
396
            else:
341
419
 
342
420
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
343
421
                 interesting_ids=None, reprocess=False, show_base=False,
344
 
                 pb=DummyProgress(), pp=None):
 
422
                 pb=DummyProgress(), pp=None, change_reporter=None):
345
423
        """Initialize the merger object and perform the merge."""
346
424
        object.__init__(self)
347
425
        self.this_tree = working_tree
 
426
        self.this_tree.lock_tree_write()
348
427
        self.base_tree = base_tree
 
428
        self.base_tree.lock_read()
349
429
        self.other_tree = other_tree
 
430
        self.other_tree.lock_read()
350
431
        self._raw_conflicts = []
351
432
        self.cooked_conflicts = []
352
433
        self.reprocess = reprocess
353
434
        self.show_base = show_base
354
435
        self.pb = pb
355
436
        self.pp = pp
 
437
        self.change_reporter = change_reporter
356
438
        if self.pp is None:
357
439
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
358
440
 
361
443
        else:
362
444
            all_ids = set(base_tree)
363
445
            all_ids.update(other_tree)
364
 
        working_tree.lock_write()
365
446
        self.tt = TreeTransform(working_tree, self.pb)
366
447
        try:
367
448
            self.pp.next_phase()
368
 
            child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
449
            child_pb = ui.ui_factory.nested_progress_bar()
369
450
            try:
370
451
                for num, file_id in enumerate(all_ids):
371
452
                    child_pb.update('Preparing file merge', num, len(all_ids))
374
455
                    self.merge_executable(file_id, file_status)
375
456
            finally:
376
457
                child_pb.finished()
377
 
                
 
458
            self.fix_root()
378
459
            self.pp.next_phase()
379
 
            child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
460
            child_pb = ui.ui_factory.nested_progress_bar()
380
461
            try:
381
462
                fs_conflicts = resolve_conflicts(self.tt, child_pb)
382
463
            finally:
383
464
                child_pb.finished()
 
465
            if change_reporter is not None:
 
466
                from bzrlib import delta
 
467
                delta.report_changes(self.tt._iter_changes(), change_reporter)
384
468
            self.cook_conflicts(fs_conflicts)
385
469
            for conflict in self.cooked_conflicts:
386
470
                warning(conflict)
388
472
            results = self.tt.apply()
389
473
            self.write_modified(results)
390
474
            try:
391
 
                working_tree.set_conflicts(ConflictList(self.cooked_conflicts))
 
475
                working_tree.add_conflicts(self.cooked_conflicts)
392
476
            except UnsupportedOperation:
393
477
                pass
394
478
        finally:
395
 
            try:
396
 
                self.tt.finalize()
397
 
            except:
398
 
                pass
399
 
            working_tree.unlock()
 
479
            self.tt.finalize()
 
480
            self.other_tree.unlock()
 
481
            self.base_tree.unlock()
 
482
            self.this_tree.unlock()
400
483
            self.pb.clear()
401
484
 
 
485
    def fix_root(self):
 
486
        try:
 
487
            self.tt.final_kind(self.tt.root)
 
488
        except NoSuchFile:
 
489
            self.tt.cancel_deletion(self.tt.root)
 
490
        if self.tt.final_file_id(self.tt.root) is None:
 
491
            self.tt.version_file(self.tt.tree_file_id(self.tt.root), 
 
492
                                 self.tt.root)
 
493
        if self.other_tree.inventory.root is None:
 
494
            return
 
495
        other_root_file_id = self.other_tree.inventory.root.file_id
 
496
        other_root = self.tt.trans_id_file_id(other_root_file_id)
 
497
        if other_root == self.tt.root:
 
498
            return
 
499
        try:
 
500
            self.tt.final_kind(other_root)
 
501
        except NoSuchFile:
 
502
            return
 
503
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
 
504
        self.tt.cancel_creation(other_root)
 
505
        self.tt.cancel_versioning(other_root)
 
506
 
 
507
    def reparent_children(self, ie, target):
 
508
        for thing, child in ie.children.iteritems():
 
509
            trans_id = self.tt.trans_id_file_id(child.file_id)
 
510
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
 
511
 
402
512
    def write_modified(self, results):
403
513
        modified_hashes = {}
404
514
        for path in results.modified_paths:
509
619
                        "conflict": other_entry}
510
620
        trans_id = self.tt.trans_id_file_id(file_id)
511
621
        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)
 
622
        if parent_id is not None:
 
623
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
624
            self.tt.adjust_path(winner_entry[name_winner].name, 
 
625
                                parent_trans_id, trans_id)
515
626
 
516
627
    def merge_contents(self, file_id):
517
628
        """Performa a merge on file_id contents."""
519
630
            if file_id not in tree:
520
631
                return (None, None)
521
632
            kind = tree.kind(file_id)
522
 
            if kind == "root_directory":
523
 
                kind = "directory"
524
633
            if kind == "file":
525
634
                contents = tree.get_file_sha1(file_id)
526
635
            elif kind == "symlink":
535
644
            parent_id = self.tt.final_parent(trans_id)
536
645
            if file_id in self.this_tree.inventory:
537
646
                self.tt.unversion_file(trans_id)
538
 
                self.tt.delete_contents(trans_id)
 
647
                if file_id in self.this_tree:
 
648
                    self.tt.delete_contents(trans_id)
539
649
            file_group = self._dump_conflicts(name, parent_id, file_id, 
540
650
                                              set_version=True)
541
651
            self._raw_conflicts.append(('contents conflict', file_group))
691
801
        if winner == "conflict":
692
802
        # There must be a None in here, if we have a conflict, but we
693
803
        # need executability since file status was not deleted.
694
 
            if self.other_tree.is_executable(file_id) is None:
 
804
            if self.executable(self.other_tree, file_id) is None:
695
805
                winner = "this"
696
806
            else:
697
807
                winner = "other"
760
870
            except KeyError:
761
871
                this_name = other_name = self.tt.final_name(trans_id)
762
872
            other_path = fp.get_path(trans_id)
763
 
            if this_parent is not None:
 
873
            if this_parent is not None and this_name is not None:
764
874
                this_parent_path = \
765
875
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
766
876
                this_path = pathjoin(this_parent_path, this_name)
780
890
 
781
891
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
782
892
                 interesting_ids=None, pb=DummyProgress(), pp=None,
783
 
                 reprocess=False):
 
893
                 reprocess=False, change_reporter=None):
784
894
        self.this_revision_tree = self._get_revision_tree(this_tree)
785
895
        self.other_revision_tree = self._get_revision_tree(other_tree)
786
896
        super(WeaveMerger, self).__init__(working_tree, this_tree, 
787
897
                                          base_tree, other_tree, 
788
898
                                          interesting_ids=interesting_ids, 
789
 
                                          pb=pb, pp=pp, reprocess=reprocess)
 
899
                                          pb=pb, pp=pp, reprocess=reprocess,
 
900
                                          change_reporter=change_reporter)
790
901
 
791
902
    def _get_revision_tree(self, tree):
792
 
        """Return a revision tree releated to this tree.
 
903
        """Return a revision tree related to this tree.
793
904
        If the tree is a WorkingTree, the basis will be returned.
794
905
        """
795
906
        if getattr(tree, 'get_weave', False) is False:
843
954
 
844
955
class Diff3Merger(Merge3Merger):
845
956
    """Three-way merger using external diff3 for text merging"""
 
957
 
846
958
    def dump_file(self, temp_dir, name, tree, file_id):
847
959
        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)
 
960
        out_file = open(out_path, "wb")
 
961
        try:
 
962
            in_file = tree.get_file(file_id)
 
963
            for line in in_file:
 
964
                out_file.write(line)
 
965
        finally:
 
966
            out_file.close()
852
967
        return out_path
853
968
 
854
969
    def text_merge(self, file_id, trans_id):
857
972
        will be dumped, and a will be conflict noted.
858
973
        """
859
974
        import bzrlib.patch
860
 
        temp_dir = mkdtemp(prefix="bzr-")
 
975
        temp_dir = osutils.mkdtemp(prefix="bzr-")
861
976
        try:
862
977
            new_file = pathjoin(temp_dir, "new")
863
978
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
866
981
            status = bzrlib.patch.diff3(new_file, this, base, other)
867
982
            if status not in (0, 1):
868
983
                raise BzrError("Unhandled diff3 exit code")
869
 
            self.tt.create_file(file(new_file, "rb"), trans_id)
 
984
            f = open(new_file, 'rb')
 
985
            try:
 
986
                self.tt.create_file(f, trans_id)
 
987
            finally:
 
988
                f.close()
870
989
            if status == 1:
871
990
                name = self.tt.final_name(trans_id)
872
991
                parent_id = self.tt.final_parent(trans_id)
873
992
                self._dump_conflicts(name, parent_id, file_id)
874
 
            self._raw_conflicts.append(('text conflict', trans_id))
 
993
                self._raw_conflicts.append(('text conflict', trans_id))
875
994
        finally:
876
 
            rmtree(temp_dir)
 
995
            osutils.rmtree(temp_dir)
877
996
 
878
997
 
879
998
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, 
 
999
                backup_files=False,
 
1000
                merge_type=Merge3Merger,
 
1001
                interesting_ids=None,
 
1002
                show_base=False,
 
1003
                reprocess=False,
885
1004
                other_rev_id=None,
886
1005
                interesting_files=None,
887
1006
                this_tree=None,
888
 
                pb=DummyProgress()):
 
1007
                pb=DummyProgress(),
 
1008
                change_reporter=None):
889
1009
    """Primary interface for merging. 
890
1010
 
891
1011
        typical use is probably 
893
1013
                     branch.get_revision_tree(base_revision))'
894
1014
        """
895
1015
    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)
 
1016
        raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
 
1017
            "parameter as of bzrlib version 0.8.")
 
1018
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
 
1019
                    pb=pb, change_reporter=change_reporter)
903
1020
    merger.backup_files = backup_files
904
1021
    merger.merge_type = merge_type
905
1022
    merger.interesting_ids = interesting_ids
908
1025
        assert not interesting_ids, ('Only supply interesting_ids'
909
1026
                                     ' or interesting_files')
910
1027
        merger._set_interesting_files(interesting_files)
911
 
    merger.show_base = show_base 
 
1028
    merger.show_base = show_base
912
1029
    merger.reprocess = reprocess
913
1030
    merger.other_rev_id = other_rev_id
914
1031
    merger.other_basis = other_rev_id
915
1032
    return merger.do_merge()
916
1033
 
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)
 
1034
def get_merge_type_registry():
 
1035
    """Merge type registry is in bzrlib.option to avoid circular imports.
 
1036
 
 
1037
    This method provides a sanctioned way to retrieve it.
 
1038
    """
 
1039
    from bzrlib import option
 
1040
    return option._merge_type_registry