~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Aaron Bentley
  • Date: 2007-03-07 23:15:10 UTC
  • mto: (1551.19.24 Aaron's mergeable stuff)
  • mto: This revision was merged to the branch mainline in revision 2325.
  • Revision ID: abentley@panoramicfeedback.com-20070307231510-jae63zsli83db3eb
Make ChangeReporter private

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:
 
62
    if revno == -1:
60
63
        revision = branch.last_revision()
61
64
    else:
62
65
        revision = branch.get_rev_id(revno)
84
87
 
85
88
 
86
89
class Merger(object):
87
 
    def __init__(self, this_branch, other_tree=None, base_tree=None, 
88
 
                 this_tree=None, pb=DummyProgress()):
 
90
    def __init__(self, this_branch, other_tree=None, base_tree=None,
 
91
                 this_tree=None, pb=DummyProgress(), change_reporter=None,
 
92
                 recurse='down'):
89
93
        object.__init__(self)
90
94
        assert this_tree is not None, "this_tree is required"
91
95
        self.this_branch = this_branch
95
99
        self.this_revision_tree = None
96
100
        self.this_basis_tree = None
97
101
        self.other_tree = other_tree
 
102
        self.other_branch = None
98
103
        self.base_tree = base_tree
99
104
        self.ignore_zero = False
100
105
        self.backup_files = False
101
106
        self.interesting_ids = None
102
107
        self.show_base = False
103
108
        self.reprocess = False
104
 
        self._pb = pb 
 
109
        self._pb = pb
105
110
        self.pp = None
106
 
 
 
111
        self.recurse = recurse
 
112
        self.change_reporter = change_reporter
107
113
 
108
114
    def revision_tree(self, revision_id):
109
115
        return self.this_branch.repository.revision_tree(revision_id)
117
123
 
118
124
        if self.other_rev_id is None:
119
125
            other_basis_tree = self.revision_tree(self.other_basis)
120
 
            changes = compare_trees(self.other_tree, other_basis_tree)
 
126
            changes = other_basis_tree.changes_from(self.other_tree)
121
127
            if changes.has_changed():
122
128
                raise WorkingTreeNotRevision(self.this_tree)
123
 
            other_rev_id = other_basis
 
129
            other_rev_id = self.other_basis
124
130
            self.other_tree = other_basis_tree
125
131
 
126
132
    def file_revisions(self, file_id):
137
143
        trees = (self.this_basis_tree, self.other_tree)
138
144
        return [get_id(tree, file_id) for tree in trees]
139
145
 
140
 
    def check_basis(self, check_clean):
141
 
        if self.this_basis is None:
142
 
            raise BzrCommandError("This branch has no commits")
 
146
    def check_basis(self, check_clean, require_commits=True):
 
147
        if self.this_basis is None and require_commits is True:
 
148
            raise BzrCommandError("This branch has no commits."
 
149
                                  " (perhaps you would prefer 'bzr pull')")
143
150
        if check_clean:
144
151
            self.compare_basis()
145
152
            if self.this_basis != self.this_rev_id:
146
153
                raise BzrCommandError("Working tree has uncommitted changes.")
147
154
 
148
155
    def compare_basis(self):
149
 
        changes = compare_trees(self.this_tree, 
150
 
                                self.this_tree.basis_tree(), False)
 
156
        changes = self.this_tree.changes_from(self.this_tree.basis_tree())
151
157
        if not changes.has_changed():
152
158
            self.this_rev_id = self.this_basis
153
159
 
167
173
        interesting_ids = set()
168
174
        for path in file_list:
169
175
            found_id = False
 
176
            # TODO: jam 20070226 The trees are not locked at this time,
 
177
            #       wouldn't it make merge faster if it locks everything in the
 
178
            #       beginning? It locks at do_merge time, but this happens
 
179
            #       before that.
170
180
            for tree in (self.this_tree, self.base_tree, self.other_tree):
171
 
                file_id = tree.inventory.path2id(path)
 
181
                file_id = tree.path2id(path)
172
182
                if file_id is not None:
173
183
                    interesting_ids.add(file_id)
174
184
                    found_id = True
184
194
        ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
185
195
        if self.other_rev_id in ancestry:
186
196
            return
187
 
        self.this_tree.add_pending_merge(self.other_rev_id)
 
197
        self.this_tree.add_parent_tree((self.other_rev_id, self.other_tree))
188
198
 
189
199
    def set_other(self, other_revision):
190
 
        other_branch, self.other_tree = _get_tree(other_revision, 
 
200
        """Set the revision and tree to merge from.
 
201
 
 
202
        This sets the other_tree, other_rev_id, other_basis attributes.
 
203
 
 
204
        :param other_revision: The [path, revision] list to merge from.
 
205
        """
 
206
        self.other_branch, self.other_tree = _get_tree(other_revision,
191
207
                                                  self.this_branch)
192
208
        if other_revision[1] == -1:
193
 
            self.other_rev_id = other_branch.last_revision()
 
209
            self.other_rev_id = self.other_branch.last_revision()
194
210
            if self.other_rev_id is None:
195
 
                raise NoCommits(other_branch)
 
211
                raise NoCommits(self.other_branch)
196
212
            self.other_basis = self.other_rev_id
197
213
        elif other_revision[1] is not None:
198
 
            self.other_rev_id = other_branch.get_rev_id(other_revision[1])
 
214
            self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
199
215
            self.other_basis = self.other_rev_id
200
216
        else:
201
217
            self.other_rev_id = None
202
 
            self.other_basis = other_branch.last_revision()
 
218
            self.other_basis = self.other_branch.last_revision()
203
219
            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)
 
220
                raise NoCommits(self.other_branch)
 
221
        if self.other_branch.base != self.this_branch.base:
 
222
            self.this_branch.fetch(self.other_branch,
 
223
                                   last_revision=self.other_basis)
 
224
 
 
225
    def set_other_revision(self, revision_id, other_branch):
 
226
        """Set 'other' based on a branch and revision id
 
227
 
 
228
        :param revision_id: The revision to use for a tree
 
229
        :param other_branch: The branch containing this tree
 
230
        """
 
231
        self.other_rev_id = revision_id
 
232
        self.other_branch = other_branch
 
233
        self.this_branch.fetch(other_branch, self.other_rev_id)
 
234
        self.other_tree = self.revision_tree(revision_id)
 
235
        self.other_basis = revision_id
 
236
 
 
237
    def find_base(self):
 
238
        self.set_base([None, None])
207
239
 
208
240
    def set_base(self, base_revision):
 
241
        """Set the base revision to use for the merge.
 
242
 
 
243
        :param base_revision: A 2-list containing a path and revision number.
 
244
        """
209
245
        mutter("doing merge() with no base_revision specified")
210
246
        if base_revision == [None, None]:
211
247
            try:
212
 
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
248
                pb = ui.ui_factory.nested_progress_bar()
213
249
                try:
214
250
                    this_repo = self.this_branch.repository
215
251
                    self.base_rev_id = common_ancestor(self.this_basis, 
237
273
                                                self.this_branch)
238
274
 
239
275
    def do_merge(self):
240
 
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree, 
241
 
                  'other_tree': self.other_tree, 
 
276
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
 
277
                  'other_tree': self.other_tree,
242
278
                  'interesting_ids': self.interesting_ids,
243
279
                  'pp': self.pp}
244
280
        if self.merge_type.requires_base:
253
289
        elif self.show_base:
254
290
            raise BzrError("Showing base is not supported for this"
255
291
                                  " merge type. %s" % self.merge_type)
256
 
        merge = self.merge_type(pb=self._pb, **kwargs)
 
292
        self.this_tree.lock_tree_write()
 
293
        if self.base_tree is not None:
 
294
            self.base_tree.lock_read()
 
295
        if self.other_tree is not None:
 
296
            self.other_tree.lock_read()
 
297
        try:
 
298
            merge = self.merge_type(pb=self._pb,
 
299
                                    change_reporter=self.change_reporter,
 
300
                                    **kwargs)
 
301
            if self.recurse == 'down':
 
302
                for path, file_id in self.this_tree.iter_references():
 
303
                    sub_tree = self.this_tree.get_nested_tree(file_id, path)
 
304
                    other_revision = self.other_tree.get_reference_revision(
 
305
                        file_id, path)
 
306
                    if  other_revision == sub_tree.last_revision():
 
307
                        continue
 
308
                    sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
 
309
                    sub_merge.merge_type = self.merge_type
 
310
                    relpath = self.this_tree.relpath(path)
 
311
                    other_branch = self.other_branch.reference_parent(file_id, relpath)
 
312
                    sub_merge.set_other_revision(other_revision, other_branch)
 
313
                    base_revision = self.base_tree.get_reference_revision(file_id)
 
314
                    sub_merge.base_tree = \
 
315
                        sub_tree.branch.repository.revision_tree(base_revision)
 
316
                    sub_merge.do_merge()
 
317
 
 
318
        finally:
 
319
            if self.other_tree is not None:
 
320
                self.other_tree.unlock()
 
321
            if self.base_tree is not None:
 
322
                self.base_tree.unlock()
 
323
            self.this_tree.unlock()
257
324
        if len(merge.cooked_conflicts) == 0:
258
325
            if not self.ignore_zero:
259
326
                note("All changes applied successfully.")
312
379
            else:
313
380
                parent = by_path[os.path.dirname(path)]
314
381
            abspath = pathjoin(self.this_tree.basedir, path)
315
 
            kind = bzrlib.osutils.file_kind(abspath)
 
382
            kind = osutils.file_kind(abspath)
316
383
            if file_id in self.base_tree.inventory:
317
384
                executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
318
385
            else:
341
408
 
342
409
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
343
410
                 interesting_ids=None, reprocess=False, show_base=False,
344
 
                 pb=DummyProgress(), pp=None):
 
411
                 pb=DummyProgress(), pp=None, change_reporter=None):
345
412
        """Initialize the merger object and perform the merge."""
346
413
        object.__init__(self)
347
414
        self.this_tree = working_tree
 
415
        self.this_tree.lock_tree_write()
348
416
        self.base_tree = base_tree
 
417
        self.base_tree.lock_read()
349
418
        self.other_tree = other_tree
 
419
        self.other_tree.lock_read()
350
420
        self._raw_conflicts = []
351
421
        self.cooked_conflicts = []
352
422
        self.reprocess = reprocess
353
423
        self.show_base = show_base
354
424
        self.pb = pb
355
425
        self.pp = pp
 
426
        self.change_reporter = change_reporter
356
427
        if self.pp is None:
357
428
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
358
429
 
361
432
        else:
362
433
            all_ids = set(base_tree)
363
434
            all_ids.update(other_tree)
364
 
        working_tree.lock_write()
365
435
        self.tt = TreeTransform(working_tree, self.pb)
366
436
        try:
367
437
            self.pp.next_phase()
368
 
            child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
438
            child_pb = ui.ui_factory.nested_progress_bar()
369
439
            try:
370
440
                for num, file_id in enumerate(all_ids):
371
441
                    child_pb.update('Preparing file merge', num, len(all_ids))
374
444
                    self.merge_executable(file_id, file_status)
375
445
            finally:
376
446
                child_pb.finished()
377
 
                
 
447
            self.fix_root()
378
448
            self.pp.next_phase()
379
 
            child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
449
            child_pb = ui.ui_factory.nested_progress_bar()
380
450
            try:
381
451
                fs_conflicts = resolve_conflicts(self.tt, child_pb)
382
452
            finally:
383
453
                child_pb.finished()
 
454
            if change_reporter is not None:
 
455
                from bzrlib import delta
 
456
                delta.report_changes(self.tt._iter_changes(), change_reporter)
384
457
            self.cook_conflicts(fs_conflicts)
385
458
            for conflict in self.cooked_conflicts:
386
459
                warning(conflict)
388
461
            results = self.tt.apply()
389
462
            self.write_modified(results)
390
463
            try:
391
 
                working_tree.set_conflicts(ConflictList(self.cooked_conflicts))
 
464
                working_tree.add_conflicts(self.cooked_conflicts)
392
465
            except UnsupportedOperation:
393
466
                pass
394
467
        finally:
395
 
            try:
396
 
                self.tt.finalize()
397
 
            except:
398
 
                pass
399
 
            working_tree.unlock()
 
468
            self.tt.finalize()
 
469
            self.other_tree.unlock()
 
470
            self.base_tree.unlock()
 
471
            self.this_tree.unlock()
400
472
            self.pb.clear()
401
473
 
 
474
    def fix_root(self):
 
475
        try:
 
476
            self.tt.final_kind(self.tt.root)
 
477
        except NoSuchFile:
 
478
            self.tt.cancel_deletion(self.tt.root)
 
479
        if self.tt.final_file_id(self.tt.root) is None:
 
480
            self.tt.version_file(self.tt.tree_file_id(self.tt.root), 
 
481
                                 self.tt.root)
 
482
        if self.other_tree.inventory.root is None:
 
483
            return
 
484
        other_root_file_id = self.other_tree.inventory.root.file_id
 
485
        other_root = self.tt.trans_id_file_id(other_root_file_id)
 
486
        if other_root == self.tt.root:
 
487
            return
 
488
        try:
 
489
            self.tt.final_kind(other_root)
 
490
        except NoSuchFile:
 
491
            return
 
492
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
 
493
        self.tt.cancel_creation(other_root)
 
494
        self.tt.cancel_versioning(other_root)
 
495
 
 
496
    def reparent_children(self, ie, target):
 
497
        for thing, child in ie.children.iteritems():
 
498
            trans_id = self.tt.trans_id_file_id(child.file_id)
 
499
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
 
500
 
402
501
    def write_modified(self, results):
403
502
        modified_hashes = {}
404
503
        for path in results.modified_paths:
509
608
                        "conflict": other_entry}
510
609
        trans_id = self.tt.trans_id_file_id(file_id)
511
610
        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)
 
611
        if parent_id is not None:
 
612
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
613
            self.tt.adjust_path(winner_entry[name_winner].name, 
 
614
                                parent_trans_id, trans_id)
515
615
 
516
616
    def merge_contents(self, file_id):
517
617
        """Performa a merge on file_id contents."""
519
619
            if file_id not in tree:
520
620
                return (None, None)
521
621
            kind = tree.kind(file_id)
522
 
            if kind == "root_directory":
523
 
                kind = "directory"
524
622
            if kind == "file":
525
623
                contents = tree.get_file_sha1(file_id)
526
624
            elif kind == "symlink":
535
633
            parent_id = self.tt.final_parent(trans_id)
536
634
            if file_id in self.this_tree.inventory:
537
635
                self.tt.unversion_file(trans_id)
538
 
                self.tt.delete_contents(trans_id)
 
636
                if file_id in self.this_tree:
 
637
                    self.tt.delete_contents(trans_id)
539
638
            file_group = self._dump_conflicts(name, parent_id, file_id, 
540
639
                                              set_version=True)
541
640
            self._raw_conflicts.append(('contents conflict', file_group))
691
790
        if winner == "conflict":
692
791
        # There must be a None in here, if we have a conflict, but we
693
792
        # need executability since file status was not deleted.
694
 
            if self.other_tree.is_executable(file_id) is None:
 
793
            if self.executable(self.other_tree, file_id) is None:
695
794
                winner = "this"
696
795
            else:
697
796
                winner = "other"
780
879
 
781
880
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
782
881
                 interesting_ids=None, pb=DummyProgress(), pp=None,
783
 
                 reprocess=False):
 
882
                 reprocess=False, change_reporter=None):
784
883
        self.this_revision_tree = self._get_revision_tree(this_tree)
785
884
        self.other_revision_tree = self._get_revision_tree(other_tree)
786
885
        super(WeaveMerger, self).__init__(working_tree, this_tree, 
787
886
                                          base_tree, other_tree, 
788
887
                                          interesting_ids=interesting_ids, 
789
 
                                          pb=pb, pp=pp, reprocess=reprocess)
 
888
                                          pb=pb, pp=pp, reprocess=reprocess,
 
889
                                          change_reporter=change_reporter)
790
890
 
791
891
    def _get_revision_tree(self, tree):
792
 
        """Return a revision tree releated to this tree.
 
892
        """Return a revision tree related to this tree.
793
893
        If the tree is a WorkingTree, the basis will be returned.
794
894
        """
795
895
        if getattr(tree, 'get_weave', False) is False:
843
943
 
844
944
class Diff3Merger(Merge3Merger):
845
945
    """Three-way merger using external diff3 for text merging"""
 
946
 
846
947
    def dump_file(self, temp_dir, name, tree, file_id):
847
948
        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)
 
949
        out_file = open(out_path, "wb")
 
950
        try:
 
951
            in_file = tree.get_file(file_id)
 
952
            for line in in_file:
 
953
                out_file.write(line)
 
954
        finally:
 
955
            out_file.close()
852
956
        return out_path
853
957
 
854
958
    def text_merge(self, file_id, trans_id):
857
961
        will be dumped, and a will be conflict noted.
858
962
        """
859
963
        import bzrlib.patch
860
 
        temp_dir = mkdtemp(prefix="bzr-")
 
964
        temp_dir = osutils.mkdtemp(prefix="bzr-")
861
965
        try:
862
966
            new_file = pathjoin(temp_dir, "new")
863
967
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
866
970
            status = bzrlib.patch.diff3(new_file, this, base, other)
867
971
            if status not in (0, 1):
868
972
                raise BzrError("Unhandled diff3 exit code")
869
 
            self.tt.create_file(file(new_file, "rb"), trans_id)
 
973
            f = open(new_file, 'rb')
 
974
            try:
 
975
                self.tt.create_file(f, trans_id)
 
976
            finally:
 
977
                f.close()
870
978
            if status == 1:
871
979
                name = self.tt.final_name(trans_id)
872
980
                parent_id = self.tt.final_parent(trans_id)
873
981
                self._dump_conflicts(name, parent_id, file_id)
874
 
            self._raw_conflicts.append(('text conflict', trans_id))
 
982
                self._raw_conflicts.append(('text conflict', trans_id))
875
983
        finally:
876
 
            rmtree(temp_dir)
 
984
            osutils.rmtree(temp_dir)
877
985
 
878
986
 
879
987
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, 
 
988
                backup_files=False,
 
989
                merge_type=Merge3Merger,
 
990
                interesting_ids=None,
 
991
                show_base=False,
 
992
                reprocess=False,
885
993
                other_rev_id=None,
886
994
                interesting_files=None,
887
995
                this_tree=None,
888
 
                pb=DummyProgress()):
 
996
                pb=DummyProgress(),
 
997
                change_reporter=None):
889
998
    """Primary interface for merging. 
890
999
 
891
1000
        typical use is probably 
893
1002
                     branch.get_revision_tree(base_revision))'
894
1003
        """
895
1004
    if this_tree is None:
896
 
        warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
 
1005
        warnings.warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
897
1006
             "bzrlib version 0.8.",
898
1007
             DeprecationWarning,
899
1008
             stacklevel=2)
900
1009
        this_tree = this_branch.bzrdir.open_workingtree()
901
 
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree, 
902
 
                    pb=pb)
 
1010
    merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
 
1011
                    pb=pb, change_reporter=change_reporter)
903
1012
    merger.backup_files = backup_files
904
1013
    merger.merge_type = merge_type
905
1014
    merger.interesting_ids = interesting_ids
908
1017
        assert not interesting_ids, ('Only supply interesting_ids'
909
1018
                                     ' or interesting_files')
910
1019
        merger._set_interesting_files(interesting_files)
911
 
    merger.show_base = show_base 
 
1020
    merger.show_base = show_base
912
1021
    merger.reprocess = reprocess
913
1022
    merger.other_rev_id = other_rev_id
914
1023
    merger.other_basis = other_rev_id
915
1024
    return merger.do_merge()
916
1025
 
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)
 
1026
def get_merge_type_registry():
 
1027
    """Merge type registry is in bzrlib.option to avoid circular imports.
 
1028
 
 
1029
    This method provides a sanctioned way to retrieve it.
 
1030
    """
 
1031
    from bzrlib import option
 
1032
    return option._merge_type_registry