51
54
# TODO: Report back as changes are merged in
54
def transform_tree(from_tree, to_tree, interesting_files=None):
55
with from_tree.lock_tree_write():
56
merge_inner(from_tree.branch, to_tree, from_tree,
57
ignore_zero=True, this_tree=from_tree,
58
interesting_files=interesting_files)
57
def transform_tree(from_tree, to_tree, interesting_ids=None):
58
from_tree.lock_tree_write()
59
operation = cleanup.OperationWithCleanups(merge_inner)
60
operation.add_cleanup(from_tree.unlock)
61
operation.run_simple(from_tree.branch, to_tree, from_tree,
62
ignore_zero=True, interesting_ids=interesting_ids, this_tree=from_tree)
61
65
class MergeHooks(hooks.Hooks):
63
67
def __init__(self):
64
68
hooks.Hooks.__init__(self, "breezy.merge", "Merger.hooks")
65
69
self.add_hook('merge_file_content',
66
"Called with a breezy.merge.Merger object to create a per file "
67
"merge object when starting a merge. "
68
"Should return either None or a subclass of "
69
"``breezy.merge.AbstractPerFileMerger``. "
70
"Such objects will then be called per file "
71
"that needs to be merged (including when one "
72
"side has deleted the file and the other has changed it). "
73
"See the AbstractPerFileMerger API docs for details on how it is "
70
"Called with a breezy.merge.Merger object to create a per file "
71
"merge object when starting a merge. "
72
"Should return either None or a subclass of "
73
"``breezy.merge.AbstractPerFileMerger``. "
74
"Such objects will then be called per file "
75
"that needs to be merged (including when one "
76
"side has deleted the file and the other has changed it). "
77
"See the AbstractPerFileMerger API docs for details on how it is "
76
80
self.add_hook('pre_merge',
77
'Called before a merge. '
78
'Receives a Merger object as the single argument.',
81
'Called before a merge. '
82
'Receives a Merger object as the single argument.',
80
84
self.add_hook('post_merge',
81
'Called after a merge. '
82
'Receives a Merger object as the single argument. '
83
'The return value is ignored.',
85
'Called after a merge. '
86
'Receives a Merger object as the single argument. '
87
'The return value is ignored.',
87
91
class AbstractPerFileMerger(object):
88
92
"""PerFileMerger objects are used by plugins extending merge for breezy.
90
94
See ``breezy.plugins.news_merge.news_merge`` for an example concrete class.
92
96
:ivar merger: The Merge3Merger performing the merge.
220
238
There are some fields hooks can access:
222
:ivar base_path: Path in base tree
223
:ivar other_path: Path in other tree
224
:ivar this_path: Path in this tree
240
:ivar file_id: the file ID of the file being merged
225
241
:ivar trans_id: the transform ID for the merge of this file
226
:ivar this_kind: kind of file in 'this' tree
227
:ivar other_kind: kind of file in 'other' tree
242
:ivar this_kind: kind of file_id in 'this' tree
243
:ivar other_kind: kind of file_id in 'other' tree
228
244
:ivar winner: one of 'this', 'other', 'conflict'
231
def __init__(self, merger, paths, trans_id, this_kind, other_kind,
247
def __init__(self, merger, file_id, trans_id, this_kind, other_kind,
233
249
self._merger = merger
235
self.base_path, self.other_path, self.this_path = paths
250
self.file_id = file_id
236
251
self.trans_id = trans_id
237
252
self.this_kind = this_kind
238
253
self.other_kind = other_kind
334
352
_set_base_is_other_ancestor)
337
def from_uncommitted(tree, other_tree, base_tree=None):
355
def from_uncommitted(tree, other_tree, pb=None, base_tree=None):
338
356
"""Return a Merger for uncommitted changes in other_tree.
340
358
:param tree: The tree to merge into
341
359
:param other_tree: The tree to get uncommitted changes from
360
:param pb: A progress indicator
342
361
:param base_tree: The basis to use for the merge. If unspecified,
343
362
other_tree.basis_tree() will be used.
345
364
if base_tree is None:
346
365
base_tree = other_tree.basis_tree()
347
merger = Merger(tree.branch, other_tree, base_tree, tree)
366
merger = Merger(tree.branch, other_tree, base_tree, tree, pb)
348
367
merger.base_rev_id = merger.base_tree.get_revision_id()
349
368
merger.other_rev_id = None
350
369
merger.other_basis = merger.base_rev_id
354
def from_mergeable(klass, tree, mergeable):
373
def from_mergeable(klass, tree, mergeable, pb):
355
374
"""Return a Merger for a bundle or merge directive.
357
376
:param tree: The tree to merge changes into
358
377
:param mergeable: A merge directive or bundle
378
:param pb: A progress indicator
360
380
mergeable.install_revisions(tree.branch.repository)
361
381
base_revision_id, other_revision_id, verified =\
364
384
if base_revision_id is not None:
365
385
if (base_revision_id != _mod_revision.NULL_REVISION and
366
386
revision_graph.is_ancestor(
367
base_revision_id, tree.branch.last_revision())):
387
base_revision_id, tree.branch.last_revision())):
368
388
base_revision_id = None
370
390
trace.warning('Performing cherrypick')
371
merger = klass.from_revision_ids(tree, other_revision_id,
372
base_revision_id, revision_graph=revision_graph)
391
merger = klass.from_revision_ids(pb, tree, other_revision_id,
392
base_revision_id, revision_graph=
373
394
return merger, verified
376
def from_revision_ids(tree, other, base=None, other_branch=None,
397
def from_revision_ids(pb, tree, other, base=None, other_branch=None,
377
398
base_branch=None, revision_graph=None,
378
399
tree_branch=None):
379
400
"""Return a Merger for revision-ids.
402
:param pb: A progress indicator
381
403
:param tree: The tree to merge changes into
382
404
:param other: The revision-id to use as OTHER
383
405
:param base: The revision-id to use as BASE. If not specified, will
437
459
def set_pending(self):
438
460
if (not self.base_is_ancestor or not self.base_is_other_ancestor
439
or self.other_rev_id is None):
461
or self.other_rev_id is None):
441
463
self._add_parent()
443
465
def _add_parent(self):
444
466
new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
445
467
new_parent_trees = []
446
with contextlib.ExitStack() as stack:
447
for revision_id in new_parents:
449
tree = self.revision_tree(revision_id)
450
except errors.NoSuchRevision:
453
stack.enter_context(tree.lock_read())
454
new_parent_trees.append((revision_id, tree))
455
self.this_tree.set_parent_trees(new_parent_trees, allow_leftmost_as_ghost=True)
468
operation = cleanup.OperationWithCleanups(
469
self.this_tree.set_parent_trees)
470
for revision_id in new_parents:
472
tree = self.revision_tree(revision_id)
473
except errors.NoSuchRevision:
477
operation.add_cleanup(tree.unlock)
478
new_parent_trees.append((revision_id, tree))
479
operation.run_simple(new_parent_trees, allow_leftmost_as_ghost=True)
457
481
def set_other(self, other_revision, possible_transports=None):
458
482
"""Set the revision and tree to merge from.
629
654
for hook in Merger.hooks['post_merge']:
631
656
if self.recurse == 'down':
632
for relpath in self.this_tree.iter_references():
633
sub_tree = self.this_tree.get_nested_tree(relpath)
657
for relpath, file_id in self.this_tree.iter_references():
658
sub_tree = self.this_tree.get_nested_tree(file_id, relpath)
634
659
other_revision = self.other_tree.get_reference_revision(
636
if other_revision == sub_tree.last_revision():
661
if other_revision == sub_tree.last_revision():
638
663
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
639
664
sub_merge.merge_type = self.merge_type
640
other_branch = self.other_tree.reference_parent(relpath)
665
other_branch = self.other_branch.reference_parent(file_id,
641
667
sub_merge.set_other_revision(other_revision, other_branch)
642
base_tree_path = _mod_tree.find_previous_path(
643
self.this_tree, self.base_tree, relpath)
644
base_revision = self.base_tree.get_reference_revision(
668
base_revision = self.base_tree.get_reference_revision(file_id)
646
669
sub_merge.base_tree = \
647
670
sub_tree.branch.repository.revision_tree(base_revision)
648
671
sub_merge.base_rev_id = base_revision
652
675
def do_merge(self):
653
with contextlib.ExitStack() as stack:
654
stack.enter_context(self.this_tree.lock_tree_write())
655
if self.base_tree is not None:
656
stack.enter_context(self.base_tree.lock_read())
657
if self.other_tree is not None:
658
stack.enter_context(self.other_tree.lock_read())
659
merge = self._do_merge_to()
676
operation = cleanup.OperationWithCleanups(self._do_merge_to)
677
self.this_tree.lock_tree_write()
678
operation.add_cleanup(self.this_tree.unlock)
679
if self.base_tree is not None:
680
self.base_tree.lock_read()
681
operation.add_cleanup(self.base_tree.unlock)
682
if self.other_tree is not None:
683
self.other_tree.lock_read()
684
operation.add_cleanup(self.other_tree.unlock)
685
merge = operation.run_simple()
660
686
if len(merge.cooked_conflicts) == 0:
661
687
if not self.ignore_zero and not trace.is_quiet():
662
688
trace.note(gettext("All changes applied successfully."))
698
720
supports_reverse_cherrypick = True
699
721
winner_idx = {"this": 2, "other": 1, "conflict": 1}
700
722
supports_lca_trees = True
701
requires_file_merge_plan = False
703
724
def __init__(self, working_tree, this_tree, base_tree, other_tree,
704
reprocess=False, show_base=False,
705
change_reporter=None, interesting_files=None, do_merge=True,
725
interesting_ids=None, reprocess=False, show_base=False,
726
pb=None, pp=None, change_reporter=None,
727
interesting_files=None, do_merge=True,
706
728
cherrypick=False, lca_trees=None, this_branch=None,
707
729
other_branch=None):
708
730
"""Initialize the merger object and perform the merge.
714
736
:param this_branch: The branch associated with this_tree. Defaults to
715
737
this_tree.branch if not supplied.
716
738
:param other_branch: The branch associated with other_tree, if any.
739
:param interesting_ids: The file_ids of files that should be
740
participate in the merge. May not be combined with
717
742
:param: reprocess If True, perform conflict-reduction processing.
718
743
:param show_base: If True, show the base revision in text conflicts.
719
744
(incompatible with reprocess)
746
:param pp: A ProgressPhase object
720
747
:param change_reporter: An object that should report changes made
721
748
:param interesting_files: The tree-relative paths of files that should
722
749
participate in the merge. If these paths refer to directories,
723
the contents of those directories will also be included. If not
724
specified, all files may participate in the
750
the contents of those directories will also be included. May not
751
be combined with interesting_ids. If neither interesting_files nor
752
interesting_ids is specified, all files may participate in the
726
754
:param lca_trees: Can be set to a dictionary of {revision_id:rev_tree}
727
755
if the ancestry was found to include a criss-cross merge.
728
756
Otherwise should be None.
730
758
object.__init__(self)
759
if interesting_files is not None and interesting_ids is not None:
761
'specify either interesting_ids or interesting_files')
731
762
if this_branch is None:
732
763
this_branch = this_tree.branch
764
self.interesting_ids = interesting_ids
733
765
self.interesting_files = interesting_files
734
766
self.working_tree = working_tree
735
767
self.this_tree = this_tree
751
783
self.cherrypick = cherrypick
787
warnings.warn("pp argument to Merge3Merger is deprecated")
789
warnings.warn("pb argument to Merge3Merger is deprecated")
755
791
def do_merge(self):
756
with contextlib.ExitStack() as stack:
757
stack.enter_context(self.working_tree.lock_tree_write())
758
stack.enter_context(self.this_tree.lock_read())
759
stack.enter_context(self.base_tree.lock_read())
760
stack.enter_context(self.other_tree.lock_read())
761
self.tt = self.working_tree.transform()
762
stack.enter_context(self.tt)
763
self._compute_transform()
764
results = self.tt.apply(no_conflicts=True)
765
self.write_modified(results)
767
self.working_tree.add_conflicts(self.cooked_conflicts)
768
except errors.UnsupportedOperation:
792
operation = cleanup.OperationWithCleanups(self._do_merge)
793
self.working_tree.lock_tree_write()
794
operation.add_cleanup(self.working_tree.unlock)
795
self.this_tree.lock_read()
796
operation.add_cleanup(self.this_tree.unlock)
797
self.base_tree.lock_read()
798
operation.add_cleanup(self.base_tree.unlock)
799
self.other_tree.lock_read()
800
operation.add_cleanup(self.other_tree.unlock)
803
def _do_merge(self, operation):
804
self.tt = transform.TreeTransform(self.working_tree, None)
805
operation.add_cleanup(self.tt.finalize)
806
self._compute_transform()
807
results = self.tt.apply(no_conflicts=True)
808
self.write_modified(results)
810
self.working_tree.add_conflicts(self.cooked_conflicts)
811
except errors.UnsupportedOperation:
771
814
def make_preview_transform(self):
772
with self.base_tree.lock_read(), self.other_tree.lock_read():
773
self.tt = self.working_tree.preview_transform()
774
self._compute_transform()
815
operation = cleanup.OperationWithCleanups(self._make_preview_transform)
816
self.base_tree.lock_read()
817
operation.add_cleanup(self.base_tree.unlock)
818
self.other_tree.lock_read()
819
operation.add_cleanup(self.other_tree.unlock)
820
return operation.run_simple()
822
def _make_preview_transform(self):
823
self.tt = transform.TransformPreview(self.working_tree)
824
self._compute_transform()
777
827
def _compute_transform(self):
778
828
if self._lca_trees is None:
779
entries = list(self._entries3())
829
entries = self._entries3()
780
830
resolver = self._three_way
782
entries = list(self._entries_lca())
832
entries = self._entries_lca()
783
833
resolver = self._lca_multi_way
784
834
# Prepare merge hooks
785
835
factories = Merger.hooks['merge_file_content']
786
836
# One hook for each registered one plus our default merger
787
837
hooks = [factory(self) for factory in factories] + [self]
788
838
self.active_hooks = [hook for hook in hooks if hook is not None]
789
with ui.ui_factory.nested_progress_bar() as child_pb:
790
for num, (file_id, changed, paths3, parents3, names3,
839
child_pb = ui.ui_factory.nested_progress_bar()
841
for num, (file_id, changed, parents3, names3,
791
842
executable3) in enumerate(entries):
792
trans_id = self.tt.trans_id_file_id(file_id)
793
843
# Try merging each entry
794
844
child_pb.update(gettext('Preparing file merge'),
795
845
num, len(entries))
796
self._merge_names(trans_id, file_id, paths3, parents3,
797
names3, resolver=resolver)
846
self._merge_names(file_id, parents3, names3, resolver=resolver)
799
file_status = self._do_merge_contents(paths3, trans_id, file_id)
848
file_status = self._do_merge_contents(file_id)
801
850
file_status = 'unmodified'
802
self._merge_executable(paths3, trans_id, executable3,
803
file_status, resolver=resolver)
851
self._merge_executable(file_id,
852
executable3, file_status, resolver=resolver)
804
855
self.tt.fixup_new_roots()
805
856
self._finish_computing_transform()
830
883
other and this. names3 is a tuple of names for base, other and this.
831
884
executable3 is a tuple of execute-bit values for base, other and this.
833
887
iterator = self.other_tree.iter_changes(self.base_tree,
834
specific_files=self.interesting_files,
835
extra_trees=[self.this_tree])
836
this_interesting_files = self.this_tree.find_related_paths_across_trees(
837
self.interesting_files, trees=[self.other_tree])
838
this_entries = dict(self.this_tree.iter_entries_by_dir(
839
specific_files=this_interesting_files))
840
for change in iterator:
841
if change.path[0] is not None:
842
this_path = _mod_tree.find_previous_path(
843
self.base_tree, self.this_tree, change.path[0])
845
this_path = _mod_tree.find_previous_path(
846
self.other_tree, self.this_tree, change.path[1])
847
this_entry = this_entries.get(this_path)
848
if this_entry is not None:
849
this_name = this_entry.name
850
this_parent = this_entry.parent_id
851
this_executable = this_entry.executable
888
specific_files=self.interesting_files,
889
extra_trees=[self.this_tree])
890
this_entries = dict((e.file_id, e) for p, e in
891
self.this_tree.iter_entries_by_dir(
892
self.interesting_ids))
893
for (file_id, paths, changed, versioned, parents, names, kind,
894
executable) in iterator:
895
if (self.interesting_ids is not None and
896
file_id not in self.interesting_ids):
898
entry = this_entries.get(file_id)
899
if entry is not None:
900
this_name = entry.name
901
this_parent = entry.parent_id
902
this_executable = entry.executable
854
905
this_parent = None
855
906
this_executable = None
856
parents3 = change.parent_id + (this_parent,)
857
names3 = change.name + (this_name,)
858
paths3 = change.path + (this_path, )
859
executable3 = change.executable + (this_executable,)
861
(change.file_id, change.changed_content, paths3,
862
parents3, names3, executable3))
907
parents3 = parents + (this_parent,)
908
names3 = names + (this_name,)
909
executable3 = executable + (this_executable,)
910
result.append((file_id, changed, parents3, names3, executable3))
864
913
def _entries_lca(self):
865
914
"""Gather data about files modified between multiple trees.
870
919
For the multi-valued entries, the format will be (BASE, [lca1, lca2])
872
:return: [(file_id, changed, paths, parents, names, executable)], where:
921
:return: [(file_id, changed, parents, names, executable)], where:
874
923
* file_id: Simple file_id of the entry
875
924
* changed: Boolean, True if the kind or contents changed else False
876
* paths: ((base, [path, in, lcas]), path_other, path_this)
877
925
* parents: ((base, [parent_id, in, lcas]), parent_id_other,
879
927
* names: ((base, [name, in, lcas]), name_in_other, name_in_this)
884
932
lookup_trees = [self.this_tree, self.base_tree]
885
933
lookup_trees.extend(self._lca_trees)
886
934
# I think we should include the lca trees as well
887
interesting_files = self.other_tree.find_related_paths_across_trees(
888
self.interesting_files, lookup_trees)
935
interesting_ids = self.other_tree.paths2ids(self.interesting_files,
890
interesting_files = None
891
from .multiwalker import MultiWalker
892
walker = MultiWalker(self.other_tree, self._lca_trees)
938
interesting_ids = self.interesting_ids
940
walker = _mod_tree.MultiWalker(self.other_tree, self._lca_trees)
894
for other_path, file_id, other_ie, lca_values in walker.iter_all():
942
base_inventory = self.base_tree.root_inventory
943
this_inventory = self.this_tree.root_inventory
944
for path, file_id, other_ie, lca_values in walker.iter_all():
895
945
# Is this modified at all from any of the other trees?
896
946
if other_ie is None:
897
947
other_ie = _none_entry
899
if interesting_files is not None and other_path not in interesting_files:
948
if interesting_ids is not None and file_id not in interesting_ids:
902
951
# If other_revision is found in any of the lcas, that means this
905
954
# we know that the ancestry is linear, and that OTHER did not
906
955
# modify anything
907
956
# See doc/developers/lca_merge_resolution.txt for details
908
# We can't use this shortcut when other_revision is None,
909
# because it may be None because things are WorkingTrees, and
910
# not because it is *actually* None.
911
is_unmodified = False
912
for lca_path, ie in lca_values:
913
if ie is not None and other_ie.is_unmodified(ie):
957
other_revision = other_ie.revision
958
if other_revision is not None:
959
# We can't use this shortcut when other_revision is None,
960
# because it may be None because things are WorkingTrees, and
961
# not because it is *actually* None.
962
is_unmodified = False
963
for lca_path, ie in lca_values:
964
if ie is not None and ie.revision == other_revision:
921
971
for lca_path, lca_ie in lca_values:
922
972
if lca_ie is None:
923
973
lca_entries.append(_none_entry)
924
lca_paths.append(None)
926
975
lca_entries.append(lca_ie)
927
lca_paths.append(lca_path)
930
base_path = self.base_tree.id2path(file_id)
931
except errors.NoSuchId:
977
if base_inventory.has_id(file_id):
978
base_ie = base_inventory[file_id]
933
980
base_ie = _none_entry
982
if this_inventory.has_id(file_id):
983
this_ie = this_inventory[file_id]
935
base_ie = next(self.base_tree.iter_entries_by_dir(specific_files=[base_path]))[1]
938
this_path = self.this_tree.id2path(file_id)
939
except errors.NoSuchId:
940
985
this_ie = _none_entry
943
this_ie = next(self.this_tree.iter_entries_by_dir(specific_files=[this_path]))[1]
946
988
lca_parent_ids = []
972
1014
content_changed = False
973
1015
elif other_ie.kind is None or other_ie.kind == 'file':
974
def get_sha1(tree, path):
978
return tree.get_file_sha1(path)
979
except errors.NoSuchFile:
981
base_sha1 = get_sha1(self.base_tree, base_path)
982
lca_sha1s = [get_sha1(tree, lca_path)
984
in zip(self._lca_trees, lca_paths)]
985
this_sha1 = get_sha1(self.this_tree, this_path)
986
other_sha1 = get_sha1(self.other_tree, other_path)
1016
def get_sha1(ie, tree):
1017
if ie.kind != 'file':
1019
return tree.get_file_sha1(file_id)
1020
base_sha1 = get_sha1(base_ie, self.base_tree)
1021
lca_sha1s = [get_sha1(ie, tree) for ie, tree
1022
in zip(lca_entries, self._lca_trees)]
1023
this_sha1 = get_sha1(this_ie, self.this_tree)
1024
other_sha1 = get_sha1(other_ie, self.other_tree)
987
1025
sha1_winner = self._lca_multi_way(
988
1026
(base_sha1, lca_sha1s), other_sha1, this_sha1,
989
1027
allow_overriding_lca=False)
991
1029
(base_ie.executable, lca_executable),
992
1030
other_ie.executable, this_ie.executable)
993
1031
if (parent_id_winner == 'this' and name_winner == 'this'
994
and sha1_winner == 'this' and exec_winner == 'this'):
1032
and sha1_winner == 'this' and exec_winner == 'this'):
995
1033
# No kind, parent, name, exec, or content change for
996
1034
# OTHER, so this node is not considered interesting
998
1036
if sha1_winner == 'this':
999
1037
content_changed = False
1000
1038
elif other_ie.kind == 'symlink':
1001
def get_target(ie, tree, path):
1039
def get_target(ie, tree):
1002
1040
if ie.kind != 'symlink':
1004
return tree.get_symlink_target(path)
1005
base_target = get_target(base_ie, self.base_tree, base_path)
1006
lca_targets = [get_target(ie, tree, lca_path) for ie, tree, lca_path
1007
in zip(lca_entries, self._lca_trees, lca_paths)]
1008
this_target = get_target(
1009
this_ie, self.this_tree, this_path)
1010
other_target = get_target(
1011
other_ie, self.other_tree, other_path)
1042
return tree.get_symlink_target(file_id)
1043
base_target = get_target(base_ie, self.base_tree)
1044
lca_targets = [get_target(ie, tree) for ie, tree
1045
in zip(lca_entries, self._lca_trees)]
1046
this_target = get_target(this_ie, self.this_tree)
1047
other_target = get_target(other_ie, self.other_tree)
1012
1048
target_winner = self._lca_multi_way(
1013
1049
(base_target, lca_targets),
1014
1050
other_target, this_target)
1015
1051
if (parent_id_winner == 'this' and name_winner == 'this'
1016
and target_winner == 'this'):
1052
and target_winner == 'this'):
1017
1053
# No kind, parent, name, or symlink target change
1018
1054
# not interesting
1031
1067
raise AssertionError('unhandled kind: %s' % other_ie.kind)
1033
1069
# If we have gotten this far, that means something has changed
1034
yield (file_id, content_changed,
1035
((base_path, lca_paths),
1036
other_path, this_path),
1070
result.append((file_id, content_changed,
1037
1071
((base_ie.parent_id, lca_parent_ids),
1038
1072
other_ie.parent_id, this_ie.parent_id),
1039
1073
((base_ie.name, lca_names),
1040
1074
other_ie.name, this_ie.name),
1041
1075
((base_ie.executable, lca_executable),
1042
1076
other_ie.executable, this_ie.executable)
1045
1080
def write_modified(self, results):
1046
if not self.working_tree.supports_merge_modified():
1048
1081
modified_hashes = {}
1049
1082
for path in results.modified_paths:
1050
wt_relpath = self.working_tree.relpath(path)
1051
if not self.working_tree.is_versioned(wt_relpath):
1083
file_id = self.working_tree.path2id(self.working_tree.relpath(path))
1053
hash = self.working_tree.get_file_sha1(wt_relpath)
1086
hash = self.working_tree.get_file_sha1(file_id)
1054
1087
if hash is None:
1056
modified_hashes[wt_relpath] = hash
1089
modified_hashes[file_id] = hash
1057
1090
self.working_tree.set_merge_modified(modified_hashes)
1093
def parent(entry, file_id):
1061
1094
"""Determine the parent for a file_id (used as a key method)"""
1062
1095
if entry is None:
1064
1097
return entry.parent_id
1100
def name(entry, file_id):
1068
1101
"""Determine the name for a file_id (used as a key method)"""
1069
1102
if entry is None:
1071
1104
return entry.name
1074
def contents_sha1(tree, path):
1107
def contents_sha1(tree, file_id):
1075
1108
"""Determine the sha1 of the file contents (used as a key method)."""
1077
return tree.get_file_sha1(path)
1078
except errors.NoSuchFile:
1109
if not tree.has_id(file_id):
1111
return tree.get_file_sha1(file_id)
1082
def executable(tree, path):
1114
def executable(tree, file_id):
1083
1115
"""Determine the executability of a file-id (used as a key method)."""
1085
if tree.kind(path) != "file":
1087
except errors.NoSuchFile:
1116
if not tree.has_id(file_id):
1089
return tree.is_executable(path)
1118
if tree.kind(file_id) != "file":
1120
return tree.is_executable(file_id)
1092
def kind(tree, path):
1123
def kind(tree, file_id):
1093
1124
"""Determine the kind of a file-id (used as a key method)."""
1095
return tree.kind(path)
1096
except errors.NoSuchFile:
1125
if not tree.has_id(file_id):
1127
return tree.kind(file_id)
1100
1130
def _three_way(base, other, this):
1162
1192
# At this point, the lcas disagree, and the tip disagree
1163
1193
return 'conflict'
1165
def _merge_names(self, trans_id, file_id, paths, parents, names, resolver):
1166
"""Perform a merge on file names and parents"""
1195
def merge_names(self, file_id):
1196
def get_entry(tree):
1198
return tree.root_inventory[file_id]
1199
except errors.NoSuchId:
1201
this_entry = get_entry(self.this_tree)
1202
other_entry = get_entry(self.other_tree)
1203
base_entry = get_entry(self.base_tree)
1204
entries = (base_entry, other_entry, this_entry)
1207
for entry in entries:
1210
parents.append(None)
1212
names.append(entry.name)
1213
parents.append(entry.parent_id)
1214
return self._merge_names(file_id, parents, names,
1215
resolver=self._three_way)
1217
def _merge_names(self, file_id, parents, names, resolver):
1218
"""Perform a merge on file_id names and parents"""
1167
1219
base_name, other_name, this_name = names
1168
1220
base_parent, other_parent, this_parent = parents
1169
unused_base_path, other_path, this_path = paths
1171
1222
name_winner = resolver(*names)
1204
1256
parent_trans_id = transform.ROOT_PARENT
1206
1258
parent_trans_id = self.tt.trans_id_file_id(parent_id)
1207
self.tt.adjust_path(name, parent_trans_id, trans_id)
1259
self.tt.adjust_path(name, parent_trans_id,
1260
self.tt.trans_id_file_id(file_id))
1209
def _do_merge_contents(self, paths, trans_id, file_id):
1262
def _do_merge_contents(self, file_id):
1210
1263
"""Performs a merge on file_id contents."""
1211
def contents_pair(tree, path):
1215
kind = tree.kind(path)
1216
except errors.NoSuchFile:
1264
def contents_pair(tree):
1265
if not tree.has_id(file_id):
1267
kind = tree.kind(file_id)
1218
1268
if kind == "file":
1219
contents = tree.get_file_sha1(path)
1269
contents = tree.get_file_sha1(file_id)
1220
1270
elif kind == "symlink":
1221
contents = tree.get_symlink_target(path)
1271
contents = tree.get_symlink_target(file_id)
1223
1273
contents = None
1224
1274
return kind, contents
1226
base_path, other_path, this_path = paths
1227
1276
# See SPOT run. run, SPOT, run.
1228
1277
# So we're not QUITE repeating ourselves; we do tricky things with
1230
other_pair = contents_pair(self.other_tree, other_path)
1231
this_pair = contents_pair(self.this_tree, this_path)
1279
base_pair = contents_pair(self.base_tree)
1280
other_pair = contents_pair(self.other_tree)
1232
1281
if self._lca_trees:
1233
(base_path, lca_paths) = base_path
1234
base_pair = contents_pair(self.base_tree, base_path)
1235
lca_pairs = [contents_pair(tree, path)
1236
for tree, path in zip(self._lca_trees, lca_paths)]
1282
this_pair = contents_pair(self.this_tree)
1283
lca_pairs = [contents_pair(tree) for tree in self._lca_trees]
1237
1284
winner = self._lca_multi_way((base_pair, lca_pairs), other_pair,
1238
1285
this_pair, allow_overriding_lca=False)
1240
base_pair = contents_pair(self.base_tree, base_path)
1241
1287
if base_pair == other_pair:
1242
1288
winner = 'this'
1244
1290
# We delayed evaluating this_pair as long as we can to avoid
1245
1291
# unnecessary sha1 calculation
1246
this_pair = contents_pair(self.this_tree, this_path)
1292
this_pair = contents_pair(self.this_tree)
1247
1293
winner = self._three_way(base_pair, other_pair, this_pair)
1248
1294
if winner == 'this':
1249
1295
# No interesting changes introduced by OTHER
1250
1296
return "unmodified"
1251
1297
# We have a hypothetical conflict, but if we have files, then we
1252
1298
# can try to merge the content
1253
params = MergeFileHookParams(
1254
self, (base_path, other_path, this_path), trans_id, this_pair[0],
1299
trans_id = self.tt.trans_id_file_id(file_id)
1300
params = MergeFileHookParams(self, file_id, trans_id, this_pair[0],
1255
1301
other_pair[0], winner)
1256
1302
hooks = self.active_hooks
1257
1303
hook_status = 'not_applicable'
1272
1318
name = self.tt.final_name(trans_id)
1273
1319
parent_id = self.tt.final_parent(trans_id)
1274
1321
inhibit_content_conflict = False
1275
if params.this_kind is None: # file_id is not in THIS
1322
if params.this_kind is None: # file_id is not in THIS
1276
1323
# Is the name used for a different file_id ?
1277
if self.this_tree.is_versioned(other_path):
1324
dupe_path = self.other_tree.id2path(file_id)
1325
this_id = self.this_tree.path2id(dupe_path)
1326
if this_id is not None:
1278
1327
# Two entries for the same path
1279
1328
keep_this = True
1280
1329
# versioning the merged file will trigger a duplicate
1282
self.tt.version_file(trans_id, file_id=file_id)
1331
self.tt.version_file(file_id, trans_id)
1283
1332
transform.create_from_tree(
1284
self.tt, trans_id, self.other_tree,
1286
filter_tree_path=self._get_filter_tree_path(other_path))
1333
self.tt, trans_id, self.other_tree, file_id,
1334
filter_tree_path=self._get_filter_tree_path(file_id))
1287
1335
inhibit_content_conflict = True
1288
elif params.other_kind is None: # file_id is not in OTHER
1336
elif params.other_kind is None: # file_id is not in OTHER
1289
1337
# Is the name used for a different file_id ?
1290
if self.other_tree.is_versioned(this_path):
1338
dupe_path = self.this_tree.id2path(file_id)
1339
other_id = self.other_tree.path2id(dupe_path)
1340
if other_id is not None:
1291
1341
# Two entries for the same path again, but here, the other
1292
1342
# entry will also be merged. We simply inhibit the
1293
1343
# 'content' conflict creation because we know OTHER will
1336
1384
def _default_other_winner_merge(self, merge_hook_params):
1337
1385
"""Replace this contents with other."""
1386
file_id = merge_hook_params.file_id
1338
1387
trans_id = merge_hook_params.trans_id
1339
if merge_hook_params.other_path is not None:
1388
if self.other_tree.has_id(file_id):
1340
1389
# OTHER changed the file
1341
1390
transform.create_from_tree(
1342
self.tt, trans_id, self.other_tree,
1343
merge_hook_params.other_path,
1344
filter_tree_path=self._get_filter_tree_path(merge_hook_params.other_path))
1391
self.tt, trans_id, self.other_tree, file_id,
1392
filter_tree_path=self._get_filter_tree_path(file_id))
1345
1393
return 'done', None
1346
elif merge_hook_params.this_path is not None:
1394
elif self.this_tree.has_id(file_id):
1347
1395
# OTHER deleted the file
1348
1396
return 'delete', None
1350
1398
raise AssertionError(
1351
'winner is OTHER, but file %r not in THIS or OTHER tree'
1352
% (merge_hook_params.base_path,))
1399
'winner is OTHER, but file_id %r not in THIS or OTHER tree'
1354
1402
def merge_contents(self, merge_hook_params):
1355
1403
"""Fallback merge logic after user installed hooks."""
1356
1404
# This function is used in merge hooks as the fallback instance.
1357
# Perhaps making this function and the functions it calls be a
1405
# Perhaps making this function and the functions it calls be a
1358
1406
# a separate class would be better.
1359
1407
if merge_hook_params.winner == 'other':
1360
1408
# OTHER is a straight winner, so replace this contents with other
1364
1412
# BASE is a file, or both converted to files, so at least we
1365
1413
# have agreement that output should be a file.
1367
self.text_merge(merge_hook_params.trans_id,
1368
merge_hook_params.paths)
1415
self.text_merge(merge_hook_params.file_id,
1416
merge_hook_params.trans_id)
1369
1417
except errors.BinaryFile:
1370
1418
return 'not_applicable', None
1371
1419
return 'done', None
1373
1421
return 'not_applicable', None
1375
def get_lines(self, tree, path):
1423
def get_lines(self, tree, file_id):
1376
1424
"""Return the lines in a file, or an empty list."""
1380
kind = tree.kind(path)
1381
except errors.NoSuchFile:
1425
if tree.has_id(file_id):
1426
return tree.get_file_lines(file_id)
1386
return tree.get_file_lines(path)
1388
def text_merge(self, trans_id, paths):
1389
"""Perform a three-way text merge on a file"""
1430
def text_merge(self, file_id, trans_id):
1431
"""Perform a three-way text merge on a file_id"""
1390
1432
# it's possible that we got here with base as a different type.
1391
1433
# if so, we just want two-way text conflicts.
1392
base_path, other_path, this_path = paths
1393
base_lines = self.get_lines(self.base_tree, base_path)
1394
other_lines = self.get_lines(self.other_tree, other_path)
1395
this_lines = self.get_lines(self.this_tree, this_path)
1434
if self.base_tree.has_id(file_id) and \
1435
self.base_tree.kind(file_id) == "file":
1436
base_lines = self.get_lines(self.base_tree, file_id)
1439
other_lines = self.get_lines(self.other_tree, file_id)
1440
this_lines = self.get_lines(self.this_tree, file_id)
1396
1441
m3 = merge3.Merge3(base_lines, this_lines, other_lines,
1397
1442
is_cherrypick=self.cherrypick)
1398
start_marker = b"!START OF MERGE CONFLICT!" + b"I HOPE THIS IS UNIQUE"
1443
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
1399
1444
if self.show_base is True:
1400
base_marker = b'|' * 7
1445
base_marker = '|' * 7
1402
1447
base_marker = None
1404
1449
def iter_merge3(retval):
1405
1450
retval["text_conflicts"] = False
1406
for line in m3.merge_lines(name_a=b"TREE",
1407
name_b=b"MERGE-SOURCE",
1408
name_base=b"BASE-REVISION",
1451
for line in m3.merge_lines(name_a = "TREE",
1452
name_b = "MERGE-SOURCE",
1453
name_base = "BASE-REVISION",
1409
1454
start_marker=start_marker,
1410
1455
base_marker=base_marker,
1411
1456
reprocess=self.reprocess):
1412
1457
if line.startswith(start_marker):
1413
1458
retval["text_conflicts"] = True
1414
yield line.replace(start_marker, b'<' * 7)
1459
yield line.replace(start_marker, '<' * 7)
1421
1466
self._raw_conflicts.append(('text conflict', trans_id))
1422
1467
name = self.tt.final_name(trans_id)
1423
1468
parent_id = self.tt.final_parent(trans_id)
1424
file_id = self.tt.final_file_id(trans_id)
1425
file_group = self._dump_conflicts(name, paths, parent_id, file_id,
1469
file_group = self._dump_conflicts(name, parent_id, file_id,
1426
1470
this_lines, base_lines,
1428
1472
file_group.append(trans_id)
1430
def _get_filter_tree_path(self, path):
1475
def _get_filter_tree_path(self, file_id):
1431
1476
if self.this_tree.supports_content_filtering():
1432
1477
# We get the path from the working tree if it exists.
1433
1478
# That fails though when OTHER is adding a file, so
1434
1479
# we fall back to the other tree to find the path if
1435
1480
# it doesn't exist locally.
1436
filter_path = _mod_tree.find_previous_path(
1437
self.other_tree, self.working_tree, path)
1438
if filter_path is None:
1441
# Skip the lookup for older formats
1482
return self.this_tree.id2path(file_id)
1483
except errors.NoSuchId:
1484
return self.other_tree.id2path(file_id)
1485
# Skip the id2path lookup for older formats
1444
def _dump_conflicts(self, name, paths, parent_id, file_id, this_lines=None,
1488
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
1445
1489
base_lines=None, other_lines=None, set_version=False,
1446
1490
no_base=False):
1447
1491
"""Emit conflict files.
1449
1493
determined automatically. If set_version is true, the .OTHER, .THIS
1450
1494
or .BASE (in that order) will be created as versioned files.
1452
base_path, other_path, this_path = paths
1453
data = [('OTHER', self.other_tree, other_path, other_lines),
1454
('THIS', self.this_tree, this_path, this_lines)]
1496
data = [('OTHER', self.other_tree, other_lines),
1497
('THIS', self.this_tree, this_lines)]
1455
1498
if not no_base:
1456
data.append(('BASE', self.base_tree, base_path, base_lines))
1499
data.append(('BASE', self.base_tree, base_lines))
1458
1501
# We need to use the actual path in the working tree of the file here,
1459
if self.this_tree.supports_content_filtering():
1460
filter_tree_path = this_path
1502
# ignoring the conflict suffixes
1504
if wt.supports_content_filtering():
1506
filter_tree_path = wt.id2path(file_id)
1507
except errors.NoSuchId:
1508
# file has been deleted
1509
filter_tree_path = None
1462
1511
# Skip the id2path lookup for older formats
1463
1512
filter_tree_path = None
1465
1514
versioned = False
1466
1515
file_group = []
1467
for suffix, tree, path, lines in data:
1468
if path is not None:
1469
trans_id = self._conflict_file(
1470
name, parent_id, path, tree, suffix, lines,
1516
for suffix, tree, lines in data:
1517
if tree.has_id(file_id):
1518
trans_id = self._conflict_file(name, parent_id, tree, file_id,
1519
suffix, lines, filter_tree_path)
1472
1520
file_group.append(trans_id)
1473
1521
if set_version and not versioned:
1474
self.tt.version_file(trans_id, file_id=file_id)
1522
self.tt.version_file(file_id, trans_id)
1475
1523
versioned = True
1476
1524
return file_group
1478
def _conflict_file(self, name, parent_id, path, tree, suffix,
1526
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
1479
1527
lines=None, filter_tree_path=None):
1480
1528
"""Emit a single conflict file."""
1481
1529
name = name + '.' + suffix
1482
1530
trans_id = self.tt.create_path(name, parent_id)
1483
transform.create_from_tree(
1484
self.tt, trans_id, tree, path,
1486
filter_tree_path=filter_tree_path)
1531
transform.create_from_tree(self.tt, trans_id, tree, file_id, lines,
1487
1533
return trans_id
1489
def _merge_executable(self, paths, trans_id, executable, file_status,
1535
def merge_executable(self, file_id, file_status):
1536
"""Perform a merge on the execute bit."""
1537
executable = [self.executable(t, file_id) for t in (self.base_tree,
1538
self.other_tree, self.this_tree)]
1539
self._merge_executable(file_id, executable, file_status,
1540
resolver=self._three_way)
1542
def _merge_executable(self, file_id, executable, file_status,
1491
1544
"""Perform a merge on the execute bit."""
1492
1545
base_executable, other_executable, this_executable = executable
1493
base_path, other_path, this_path = paths
1494
1546
if file_status == "deleted":
1496
1548
winner = resolver(*executable)
1497
1549
if winner == "conflict":
1498
# There must be a None in here, if we have a conflict, but we
1499
# need executability since file status was not deleted.
1500
if other_path is None:
1550
# There must be a None in here, if we have a conflict, but we
1551
# need executability since file status was not deleted.
1552
if self.executable(self.other_tree, file_id) is None:
1501
1553
winner = "this"
1503
1555
winner = "other"
1504
1556
if winner == 'this' and file_status != "modified":
1558
trans_id = self.tt.trans_id_file_id(file_id)
1506
1559
if self.tt.final_kind(trans_id) != "file":
1508
1561
if winner == "this":
1509
1562
executability = this_executable
1511
if other_path is not None:
1564
if self.other_tree.has_id(file_id):
1512
1565
executability = other_executable
1513
elif this_path is not None:
1566
elif self.this_tree.has_id(file_id):
1514
1567
executability = this_executable
1515
elif base_path is not None:
1568
elif self.base_tree_has_id(file_id):
1516
1569
executability = base_executable
1517
1570
if executability is not None:
1571
trans_id = self.tt.trans_id_file_id(file_id)
1518
1572
self.tt.set_executability(executability, trans_id)
1520
1574
def cook_conflicts(self, fs_conflicts):
1526
1580
conflict_type = conflict[0]
1527
1581
if conflict_type == 'path conflict':
1528
1582
(trans_id, file_id,
1529
this_parent, this_name,
1530
other_parent, other_name) = conflict[1:]
1583
this_parent, this_name,
1584
other_parent, other_name) = conflict[1:]
1531
1585
if this_parent is None or this_name is None:
1532
1586
this_path = '<deleted>'
1534
parent_path = fp.get_path(
1588
parent_path = fp.get_path(
1535
1589
self.tt.trans_id_file_id(this_parent))
1536
1590
this_path = osutils.pathjoin(parent_path, this_name)
1537
1591
if other_parent is None or other_name is None:
1538
1592
other_path = '<deleted>'
1540
if other_parent == self.other_tree.path2id(''):
1594
if other_parent == self.other_tree.get_root_id():
1541
1595
# The tree transform doesn't know about the other root,
1542
1596
# so we special case here to avoid a NoFinalPath
1544
1598
parent_path = ''
1546
parent_path = fp.get_path(
1600
parent_path = fp.get_path(
1547
1601
self.tt.trans_id_file_id(other_parent))
1548
1602
other_path = osutils.pathjoin(parent_path, other_name)
1549
1603
c = _mod_conflicts.Conflict.factory(
1652
1703
class LCAMerger(WeaveMerger):
1654
requires_file_merge_plan = True
1656
def _generate_merge_plan(self, this_path, base):
1657
return self.this_tree.plan_file_lca_merge(this_path, self.other_tree,
1705
def _generate_merge_plan(self, file_id, base):
1706
return self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1661
1709
class Diff3Merger(Merge3Merger):
1662
1710
"""Three-way merger using external diff3 for text merging"""
1664
requires_file_merge_plan = False
1666
def dump_file(self, temp_dir, name, tree, path):
1712
def dump_file(self, temp_dir, name, tree, file_id):
1667
1713
out_path = osutils.pathjoin(temp_dir, name)
1668
with open(out_path, "wb") as out_file:
1669
in_file = tree.get_file(path)
1714
out_file = open(out_path, "wb")
1716
in_file = tree.get_file(file_id)
1670
1717
for line in in_file:
1671
1718
out_file.write(line)
1672
1721
return out_path
1674
def text_merge(self, trans_id, paths):
1723
def text_merge(self, file_id, trans_id):
1675
1724
"""Perform a diff3 merge using a specified file-id and trans-id.
1676
1725
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
1677
1726
will be dumped, and a will be conflict noted.
1679
1728
import breezy.patch
1680
base_path, other_path, this_path = paths
1681
1729
temp_dir = osutils.mkdtemp(prefix="bzr-")
1683
1731
new_file = osutils.pathjoin(temp_dir, "new")
1684
this = self.dump_file(
1685
temp_dir, "this", self.this_tree, this_path)
1686
base = self.dump_file(
1687
temp_dir, "base", self.base_tree, base_path)
1688
other = self.dump_file(
1689
temp_dir, "other", self.other_tree, other_path)
1732
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1733
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1734
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1690
1735
status = breezy.patch.diff3(new_file, this, base, other)
1691
1736
if status not in (0, 1):
1692
1737
raise errors.BzrError("Unhandled diff3 exit code")
1693
with open(new_file, 'rb') as f:
1738
f = open(new_file, 'rb')
1694
1740
self.tt.create_file(f, trans_id)
1695
1743
if status == 1:
1696
1744
name = self.tt.final_name(trans_id)
1697
1745
parent_id = self.tt.final_parent(trans_id)
1698
file_id = self.tt.final_file_id(trans_id)
1699
self._dump_conflicts(name, paths, parent_id, file_id)
1746
self._dump_conflicts(name, parent_id, file_id)
1700
1747
self._raw_conflicts.append(('text conflict', trans_id))
1702
1749
osutils.rmtree(temp_dir)
1812
1859
super(MergeIntoMergeType, self).__init__(*args, **kwargs)
1814
1861
def _compute_transform(self):
1815
with ui.ui_factory.nested_progress_bar() as child_pb:
1862
child_pb = ui.ui_factory.nested_progress_bar()
1816
1864
entries = self._entries_to_incorporate()
1817
1865
entries = list(entries)
1818
for num, (entry, parent_id, relpath) in enumerate(entries):
1819
child_pb.update(gettext('Preparing file merge'),
1866
for num, (entry, parent_id) in enumerate(entries):
1867
child_pb.update(gettext('Preparing file merge'), num, len(entries))
1821
1868
parent_trans_id = self.tt.trans_id_file_id(parent_id)
1822
path = osutils.pathjoin(self._source_subpath, relpath)
1823
trans_id = transform.new_by_entry(path, self.tt, entry,
1824
parent_trans_id, self.other_tree)
1869
trans_id = transform.new_by_entry(self.tt, entry,
1870
parent_trans_id, self.other_tree)
1825
1873
self._finish_computing_transform()
1827
1875
def _entries_to_incorporate(self):
1828
1876
"""Yields pairs of (inventory_entry, new_parent)."""
1829
subdir_id = self.other_tree.path2id(self._source_subpath)
1877
other_inv = self.other_tree.root_inventory
1878
subdir_id = other_inv.path2id(self._source_subpath)
1830
1879
if subdir_id is None:
1831
1880
# XXX: The error would be clearer if it gave the URL of the source
1832
1881
# branch, but we don't have a reference to that here.
1833
1882
raise PathNotInTree(self._source_subpath, "Source tree")
1834
subdir = next(self.other_tree.iter_entries_by_dir(
1835
specific_files=[self._source_subpath]))[1]
1883
subdir = other_inv[subdir_id]
1836
1884
parent_in_target = osutils.dirname(self._target_subdir)
1837
1885
target_id = self.this_tree.path2id(parent_in_target)
1838
1886
if target_id is None:
1854
1898
# an edge case, so we don't do anything special for those. We let
1855
1899
# them cause conflicts.
1856
1900
merge_into_root.file_id = generate_ids.gen_file_id(name_in_target)
1857
yield (merge_into_root, target_id, '')
1901
yield (merge_into_root, target_id)
1858
1902
if subdir.kind != 'directory':
1859
1903
# No children, so we are done.
1861
for path, entry in self.other_tree.root_inventory.iter_entries_by_dir(subdir_id):
1905
for ignored_path, entry in other_inv.iter_entries_by_dir(subdir_id):
1862
1906
parent_id = entry.parent_id
1863
1907
if parent_id == subdir.file_id:
1864
1908
# The root's parent ID has changed, so make sure children of
1865
1909
# the root refer to the new ID.
1866
1910
parent_id = merge_into_root.file_id
1867
yield (entry, parent_id, path)
1911
yield (entry, parent_id)
1870
1914
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1871
1915
backup_files=False,
1872
1916
merge_type=Merge3Merger,
1917
interesting_ids=None,
1873
1918
show_base=False,
1874
1919
reprocess=False,
1875
1920
other_rev_id=None,
1876
1921
interesting_files=None,
1877
1922
this_tree=None,
1878
1924
change_reporter=None):
1879
1925
"""Primary interface for merging.