63
69
def __init__(self):
64
70
hooks.Hooks.__init__(self, "breezy.merge", "Merger.hooks")
65
71
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 "
72
"Called with a breezy.merge.Merger object to create a per file "
73
"merge object when starting a merge. "
74
"Should return either None or a subclass of "
75
"``breezy.merge.AbstractPerFileMerger``. "
76
"Such objects will then be called per file "
77
"that needs to be merged (including when one "
78
"side has deleted the file and the other has changed it). "
79
"See the AbstractPerFileMerger API docs for details on how it is "
76
82
self.add_hook('pre_merge',
77
'Called before a merge. '
78
'Receives a Merger object as the single argument.',
83
'Called before a merge. '
84
'Receives a Merger object as the single argument.',
80
86
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.',
87
'Called after a merge. '
88
'Receives a Merger object as the single argument. '
89
'The return value is ignored.',
87
93
class AbstractPerFileMerger(object):
88
94
"""PerFileMerger objects are used by plugins extending merge for breezy.
90
96
See ``breezy.plugins.news_merge.news_merge`` for an example concrete class.
92
98
:ivar merger: The Merge3Merger performing the merge.
220
240
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
242
:ivar file_id: the file ID of the file being merged
225
243
: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
244
:ivar this_kind: kind of file_id in 'this' tree
245
:ivar other_kind: kind of file_id in 'other' tree
228
246
:ivar winner: one of 'this', 'other', 'conflict'
231
def __init__(self, merger, paths, trans_id, this_kind, other_kind,
249
def __init__(self, merger, file_id, trans_id, this_kind, other_kind,
233
251
self._merger = merger
235
self.base_path, self.other_path, self.this_path = paths
252
self.file_id = file_id
236
253
self.trans_id = trans_id
237
254
self.this_kind = this_kind
238
255
self.other_kind = other_kind
437
456
def set_pending(self):
438
457
if (not self.base_is_ancestor or not self.base_is_other_ancestor
439
or self.other_rev_id is None):
458
or self.other_rev_id is None):
441
460
self._add_parent()
443
462
def _add_parent(self):
444
463
new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
445
464
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)
465
operation = cleanup.OperationWithCleanups(
466
self.this_tree.set_parent_trees)
467
for revision_id in new_parents:
469
tree = self.revision_tree(revision_id)
470
except errors.NoSuchRevision:
474
operation.add_cleanup(tree.unlock)
475
new_parent_trees.append((revision_id, tree))
476
operation.run_simple(new_parent_trees, allow_leftmost_as_ghost=True)
457
478
def set_other(self, other_revision, possible_transports=None):
458
479
"""Set the revision and tree to merge from.
629
650
for hook in Merger.hooks['post_merge']:
631
652
if self.recurse == 'down':
632
for relpath in self.this_tree.iter_references():
633
sub_tree = self.this_tree.get_nested_tree(relpath)
653
for relpath, file_id in self.this_tree.iter_references():
654
sub_tree = self.this_tree.get_nested_tree(relpath, file_id)
634
655
other_revision = self.other_tree.get_reference_revision(
636
if other_revision == sub_tree.last_revision():
657
if other_revision == sub_tree.last_revision():
638
659
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
639
660
sub_merge.merge_type = self.merge_type
640
other_branch = self.other_tree.reference_parent(relpath)
661
other_branch = self.other_branch.reference_parent(file_id,
641
663
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)
664
base_tree_path = self.base_tree.id2path(file_id)
644
665
base_revision = self.base_tree.get_reference_revision(
666
base_tree_path, file_id)
646
667
sub_merge.base_tree = \
647
668
sub_tree.branch.repository.revision_tree(base_revision)
648
669
sub_merge.base_rev_id = base_revision
652
673
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()
674
operation = cleanup.OperationWithCleanups(self._do_merge_to)
675
self.this_tree.lock_tree_write()
676
operation.add_cleanup(self.this_tree.unlock)
677
if self.base_tree is not None:
678
self.base_tree.lock_read()
679
operation.add_cleanup(self.base_tree.unlock)
680
if self.other_tree is not None:
681
self.other_tree.lock_read()
682
operation.add_cleanup(self.other_tree.unlock)
683
merge = operation.run_simple()
660
684
if len(merge.cooked_conflicts) == 0:
661
685
if not self.ignore_zero and not trace.is_quiet():
662
686
trace.note(gettext("All changes applied successfully."))
714
733
:param this_branch: The branch associated with this_tree. Defaults to
715
734
this_tree.branch if not supplied.
716
735
:param other_branch: The branch associated with other_tree, if any.
736
:param interesting_ids: The file_ids of files that should be
737
participate in the merge. May not be combined with
717
739
:param: reprocess If True, perform conflict-reduction processing.
718
740
:param show_base: If True, show the base revision in text conflicts.
719
741
(incompatible with reprocess)
720
742
:param change_reporter: An object that should report changes made
721
743
:param interesting_files: The tree-relative paths of files that should
722
744
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
745
the contents of those directories will also be included. May not
746
be combined with interesting_ids. If neither interesting_files nor
747
interesting_ids is specified, all files may participate in the
726
749
:param lca_trees: Can be set to a dictionary of {revision_id:rev_tree}
727
750
if the ancestry was found to include a criss-cross merge.
728
751
Otherwise should be None.
730
753
object.__init__(self)
754
if interesting_files is not None and interesting_ids is not None:
756
'specify either interesting_ids or interesting_files')
731
757
if this_branch is None:
732
758
this_branch = this_tree.branch
759
self.interesting_ids = interesting_ids
733
760
self.interesting_files = interesting_files
734
761
self.working_tree = working_tree
735
762
self.this_tree = this_tree
755
782
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:
783
operation = cleanup.OperationWithCleanups(self._do_merge)
784
self.working_tree.lock_tree_write()
785
operation.add_cleanup(self.working_tree.unlock)
786
self.this_tree.lock_read()
787
operation.add_cleanup(self.this_tree.unlock)
788
self.base_tree.lock_read()
789
operation.add_cleanup(self.base_tree.unlock)
790
self.other_tree.lock_read()
791
operation.add_cleanup(self.other_tree.unlock)
794
def _do_merge(self, operation):
795
self.tt = transform.TreeTransform(self.working_tree, None)
796
operation.add_cleanup(self.tt.finalize)
797
self._compute_transform()
798
results = self.tt.apply(no_conflicts=True)
799
self.write_modified(results)
801
self.working_tree.add_conflicts(self.cooked_conflicts)
802
except errors.UnsupportedOperation:
771
805
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()
806
operation = cleanup.OperationWithCleanups(self._make_preview_transform)
807
self.base_tree.lock_read()
808
operation.add_cleanup(self.base_tree.unlock)
809
self.other_tree.lock_read()
810
operation.add_cleanup(self.other_tree.unlock)
811
return operation.run_simple()
813
def _make_preview_transform(self):
814
self.tt = transform.TransformPreview(self.working_tree)
815
self._compute_transform()
777
818
def _compute_transform(self):
778
819
if self._lca_trees is None:
779
entries = list(self._entries3())
820
entries = self._entries3()
780
821
resolver = self._three_way
782
entries = list(self._entries_lca())
823
entries = self._entries_lca()
783
824
resolver = self._lca_multi_way
784
825
# Prepare merge hooks
785
826
factories = Merger.hooks['merge_file_content']
786
827
# One hook for each registered one plus our default merger
787
828
hooks = [factory(self) for factory in factories] + [self]
788
829
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,
830
child_pb = ui.ui_factory.nested_progress_bar()
832
for num, (file_id, changed, parents3, names3,
791
833
executable3) in enumerate(entries):
792
trans_id = self.tt.trans_id_file_id(file_id)
793
834
# Try merging each entry
794
835
child_pb.update(gettext('Preparing file merge'),
795
836
num, len(entries))
796
self._merge_names(trans_id, file_id, paths3, parents3,
797
names3, resolver=resolver)
837
self._merge_names(file_id, parents3, names3, resolver=resolver)
799
file_status = self._do_merge_contents(paths3, trans_id, file_id)
839
file_status = self._do_merge_contents(file_id)
801
841
file_status = 'unmodified'
802
self._merge_executable(paths3, trans_id, executable3,
803
file_status, resolver=resolver)
842
self._merge_executable(file_id,
843
executable3, file_status, resolver=resolver)
804
846
self.tt.fixup_new_roots()
805
847
self._finish_computing_transform()
830
874
other and this. names3 is a tuple of names for base, other and this.
831
875
executable3 is a tuple of execute-bit values for base, other and this.
833
878
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
879
specific_files=self.interesting_files,
880
extra_trees=[self.this_tree])
881
this_entries = dict((e.file_id, e) for p, e in
882
self.this_tree.iter_entries_by_dir(
883
self.interesting_ids))
884
for (file_id, paths, changed, versioned, parents, names, kind,
885
executable) in iterator:
886
if (self.interesting_ids is not None and
887
file_id not in self.interesting_ids):
889
entry = this_entries.get(file_id)
890
if entry is not None:
891
this_name = entry.name
892
this_parent = entry.parent_id
893
this_executable = entry.executable
854
896
this_parent = None
855
897
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))
898
parents3 = parents + (this_parent,)
899
names3 = names + (this_name,)
900
executable3 = executable + (this_executable,)
901
result.append((file_id, changed, parents3, names3, executable3))
864
904
def _entries_lca(self):
865
905
"""Gather data about files modified between multiple trees.
870
910
For the multi-valued entries, the format will be (BASE, [lca1, lca2])
872
:return: [(file_id, changed, paths, parents, names, executable)], where:
912
:return: [(file_id, changed, parents, names, executable)], where:
874
914
* file_id: Simple file_id of the entry
875
915
* changed: Boolean, True if the kind or contents changed else False
876
* paths: ((base, [path, in, lcas]), path_other, path_this)
877
916
* parents: ((base, [parent_id, in, lcas]), parent_id_other,
879
918
* names: ((base, [name, in, lcas]), name_in_other, name_in_this)
884
923
lookup_trees = [self.this_tree, self.base_tree]
885
924
lookup_trees.extend(self._lca_trees)
886
925
# 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)
926
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)
929
interesting_ids = self.interesting_ids
931
walker = _mod_tree.MultiWalker(self.other_tree, self._lca_trees)
894
for other_path, file_id, other_ie, lca_values in walker.iter_all():
933
base_inventory = self.base_tree.root_inventory
934
this_inventory = self.this_tree.root_inventory
935
for path, file_id, other_ie, lca_values in walker.iter_all():
895
936
# Is this modified at all from any of the other trees?
896
937
if other_ie is None:
897
938
other_ie = _none_entry
899
if interesting_files is not None and other_path not in interesting_files:
939
if interesting_ids is not None and file_id not in interesting_ids:
902
942
# If other_revision is found in any of the lcas, that means this
905
945
# we know that the ancestry is linear, and that OTHER did not
906
946
# modify anything
907
947
# 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):
948
other_revision = other_ie.revision
949
if other_revision is not None:
950
# We can't use this shortcut when other_revision is None,
951
# because it may be None because things are WorkingTrees, and
952
# not because it is *actually* None.
953
is_unmodified = False
954
for lca_path, ie in lca_values:
955
if ie is not None and ie.revision == other_revision:
921
962
for lca_path, lca_ie in lca_values:
922
963
if lca_ie is None:
923
964
lca_entries.append(_none_entry)
924
lca_paths.append(None)
926
966
lca_entries.append(lca_ie)
927
lca_paths.append(lca_path)
930
base_path = self.base_tree.id2path(file_id)
931
except errors.NoSuchId:
968
if base_inventory.has_id(file_id):
969
base_ie = base_inventory[file_id]
933
971
base_ie = _none_entry
973
if this_inventory.has_id(file_id):
974
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
976
this_ie = _none_entry
943
this_ie = next(self.this_tree.iter_entries_by_dir(specific_files=[this_path]))[1]
946
979
lca_parent_ids = []
972
1005
content_changed = False
973
1006
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)
1007
def get_sha1(ie, tree):
1008
if ie.kind != 'file':
1010
return tree.get_file_sha1(tree.id2path(file_id), file_id)
1011
base_sha1 = get_sha1(base_ie, self.base_tree)
1012
lca_sha1s = [get_sha1(ie, tree) for ie, tree
1013
in zip(lca_entries, self._lca_trees)]
1014
this_sha1 = get_sha1(this_ie, self.this_tree)
1015
other_sha1 = get_sha1(other_ie, self.other_tree)
987
1016
sha1_winner = self._lca_multi_way(
988
1017
(base_sha1, lca_sha1s), other_sha1, this_sha1,
989
1018
allow_overriding_lca=False)
991
1020
(base_ie.executable, lca_executable),
992
1021
other_ie.executable, this_ie.executable)
993
1022
if (parent_id_winner == 'this' and name_winner == 'this'
994
and sha1_winner == 'this' and exec_winner == 'this'):
1023
and sha1_winner == 'this' and exec_winner == 'this'):
995
1024
# No kind, parent, name, exec, or content change for
996
1025
# OTHER, so this node is not considered interesting
998
1027
if sha1_winner == 'this':
999
1028
content_changed = False
1000
1029
elif other_ie.kind == 'symlink':
1001
def get_target(ie, tree, path):
1030
def get_target(ie, tree):
1002
1031
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)
1033
path = tree.id2path(file_id)
1034
return tree.get_symlink_target(path, file_id)
1035
base_target = get_target(base_ie, self.base_tree)
1036
lca_targets = [get_target(ie, tree) for ie, tree
1037
in zip(lca_entries, self._lca_trees)]
1038
this_target = get_target(this_ie, self.this_tree)
1039
other_target = get_target(other_ie, self.other_tree)
1012
1040
target_winner = self._lca_multi_way(
1013
1041
(base_target, lca_targets),
1014
1042
other_target, this_target)
1015
1043
if (parent_id_winner == 'this' and name_winner == 'this'
1016
and target_winner == 'this'):
1044
and target_winner == 'this'):
1017
1045
# No kind, parent, name, or symlink target change
1018
1046
# not interesting
1031
1059
raise AssertionError('unhandled kind: %s' % other_ie.kind)
1033
1061
# 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),
1062
result.append((file_id, content_changed,
1037
1063
((base_ie.parent_id, lca_parent_ids),
1038
1064
other_ie.parent_id, this_ie.parent_id),
1039
1065
((base_ie.name, lca_names),
1040
1066
other_ie.name, this_ie.name),
1041
1067
((base_ie.executable, lca_executable),
1042
1068
other_ie.executable, this_ie.executable)
1045
1072
def write_modified(self, results):
1046
if not self.working_tree.supports_merge_modified():
1048
1073
modified_hashes = {}
1049
1074
for path in results.modified_paths:
1050
1075
wt_relpath = self.working_tree.relpath(path)
1051
if not self.working_tree.is_versioned(wt_relpath):
1076
file_id = self.working_tree.path2id(wt_relpath)
1053
hash = self.working_tree.get_file_sha1(wt_relpath)
1079
hash = self.working_tree.get_file_sha1(wt_relpath, file_id)
1054
1080
if hash is None:
1056
modified_hashes[wt_relpath] = hash
1082
modified_hashes[file_id] = hash
1057
1083
self.working_tree.set_merge_modified(modified_hashes)
1086
def parent(entry, file_id):
1061
1087
"""Determine the parent for a file_id (used as a key method)"""
1062
1088
if entry is None:
1064
1090
return entry.parent_id
1093
def name(entry, file_id):
1068
1094
"""Determine the name for a file_id (used as a key method)"""
1069
1095
if entry is None:
1071
1097
return entry.name
1074
def contents_sha1(tree, path):
1100
def contents_sha1(tree, file_id):
1075
1101
"""Determine the sha1 of the file contents (used as a key method)."""
1077
return tree.get_file_sha1(path)
1078
except errors.NoSuchFile:
1103
path = tree.id2path(file_id)
1104
except errors.NoSuchId:
1106
return tree.get_file_sha1(path, file_id)
1082
def executable(tree, path):
1109
def executable(tree, file_id):
1083
1110
"""Determine the executability of a file-id (used as a key method)."""
1085
if tree.kind(path) != "file":
1087
except errors.NoSuchFile:
1112
path = tree.id2path(file_id)
1113
except errors.NoSuchId:
1089
return tree.is_executable(path)
1115
if tree.kind(path, file_id) != "file":
1117
return tree.is_executable(path, file_id)
1092
def kind(tree, path):
1120
def kind(tree, path, file_id):
1093
1121
"""Determine the kind of a file-id (used as a key method)."""
1095
return tree.kind(path)
1096
except errors.NoSuchFile:
1123
path = tree.id2path(file_id)
1124
except errors.NoSuchId:
1126
return tree.kind(path, file_id)
1100
1129
def _three_way(base, other, this):
1162
1191
# At this point, the lcas disagree, and the tip disagree
1163
1192
return 'conflict'
1165
def _merge_names(self, trans_id, file_id, paths, parents, names, resolver):
1166
"""Perform a merge on file names and parents"""
1194
def merge_names(self, file_id):
1195
def get_entry(tree):
1197
return tree.root_inventory[file_id]
1198
except errors.NoSuchId:
1200
this_entry = get_entry(self.this_tree)
1201
other_entry = get_entry(self.other_tree)
1202
base_entry = get_entry(self.base_tree)
1203
entries = (base_entry, other_entry, this_entry)
1206
for entry in entries:
1209
parents.append(None)
1211
names.append(entry.name)
1212
parents.append(entry.parent_id)
1213
return self._merge_names(file_id, parents, names,
1214
resolver=self._three_way)
1216
def _merge_names(self, file_id, parents, names, resolver):
1217
"""Perform a merge on file_id names and parents"""
1167
1218
base_name, other_name, this_name = names
1168
1219
base_parent, other_parent, this_parent = parents
1169
unused_base_path, other_path, this_path = paths
1171
1221
name_winner = resolver(*names)
1204
1255
parent_trans_id = transform.ROOT_PARENT
1206
1257
parent_trans_id = self.tt.trans_id_file_id(parent_id)
1207
self.tt.adjust_path(name, parent_trans_id, trans_id)
1258
self.tt.adjust_path(name, parent_trans_id,
1259
self.tt.trans_id_file_id(file_id))
1209
def _do_merge_contents(self, paths, trans_id, file_id):
1261
def _do_merge_contents(self, file_id):
1210
1262
"""Performs a merge on file_id contents."""
1211
def contents_pair(tree, path):
1263
def contents_pair(tree):
1265
path = tree.id2path(file_id)
1266
except errors.NoSuchId:
1213
1267
return (None, None)
1215
kind = tree.kind(path)
1269
kind = tree.kind(path, file_id)
1216
1270
except errors.NoSuchFile:
1217
1271
return (None, None)
1218
1272
if kind == "file":
1219
contents = tree.get_file_sha1(path)
1273
contents = tree.get_file_sha1(path, file_id)
1220
1274
elif kind == "symlink":
1221
contents = tree.get_symlink_target(path)
1275
contents = tree.get_symlink_target(path, file_id)
1223
1277
contents = None
1224
1278
return kind, contents
1226
base_path, other_path, this_path = paths
1227
1280
# See SPOT run. run, SPOT, run.
1228
1281
# 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)
1283
base_pair = contents_pair(self.base_tree)
1284
other_pair = contents_pair(self.other_tree)
1232
1285
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)]
1286
this_pair = contents_pair(self.this_tree)
1287
lca_pairs = [contents_pair(tree) for tree in self._lca_trees]
1237
1288
winner = self._lca_multi_way((base_pair, lca_pairs), other_pair,
1238
1289
this_pair, allow_overriding_lca=False)
1240
base_pair = contents_pair(self.base_tree, base_path)
1241
1291
if base_pair == other_pair:
1242
1292
winner = 'this'
1244
1294
# We delayed evaluating this_pair as long as we can to avoid
1245
1295
# unnecessary sha1 calculation
1246
this_pair = contents_pair(self.this_tree, this_path)
1296
this_pair = contents_pair(self.this_tree)
1247
1297
winner = self._three_way(base_pair, other_pair, this_pair)
1248
1298
if winner == 'this':
1249
1299
# No interesting changes introduced by OTHER
1250
1300
return "unmodified"
1251
1301
# We have a hypothetical conflict, but if we have files, then we
1252
1302
# can try to merge the content
1253
params = MergeFileHookParams(
1254
self, (base_path, other_path, this_path), trans_id, this_pair[0],
1303
trans_id = self.tt.trans_id_file_id(file_id)
1304
params = MergeFileHookParams(self, file_id, trans_id, this_pair[0],
1255
1305
other_pair[0], winner)
1256
1306
hooks = self.active_hooks
1257
1307
hook_status = 'not_applicable'
1272
1322
name = self.tt.final_name(trans_id)
1273
1323
parent_id = self.tt.final_parent(trans_id)
1274
1325
inhibit_content_conflict = False
1275
if params.this_kind is None: # file_id is not in THIS
1326
if params.this_kind is None: # file_id is not in THIS
1276
1327
# Is the name used for a different file_id ?
1277
if self.this_tree.is_versioned(other_path):
1328
dupe_path = self.other_tree.id2path(file_id)
1329
this_id = self.this_tree.path2id(dupe_path)
1330
if this_id is not None:
1278
1331
# Two entries for the same path
1279
1332
keep_this = True
1280
1333
# versioning the merged file will trigger a duplicate
1282
self.tt.version_file(trans_id, file_id=file_id)
1335
self.tt.version_file(file_id, trans_id)
1283
1336
transform.create_from_tree(
1284
1337
self.tt, trans_id, self.other_tree,
1286
filter_tree_path=self._get_filter_tree_path(other_path))
1338
self.other_tree.id2path(file_id), file_id=file_id,
1339
filter_tree_path=self._get_filter_tree_path(file_id))
1287
1340
inhibit_content_conflict = True
1288
elif params.other_kind is None: # file_id is not in OTHER
1341
elif params.other_kind is None: # file_id is not in OTHER
1289
1342
# Is the name used for a different file_id ?
1290
if self.other_tree.is_versioned(this_path):
1343
dupe_path = self.this_tree.id2path(file_id)
1344
other_id = self.other_tree.path2id(dupe_path)
1345
if other_id is not None:
1291
1346
# Two entries for the same path again, but here, the other
1292
1347
# entry will also be merged. We simply inhibit the
1293
1348
# 'content' conflict creation because we know OTHER will
1336
1389
def _default_other_winner_merge(self, merge_hook_params):
1337
1390
"""Replace this contents with other."""
1391
file_id = merge_hook_params.file_id
1338
1392
trans_id = merge_hook_params.trans_id
1339
if merge_hook_params.other_path is not None:
1393
if self.other_tree.has_id(file_id):
1340
1394
# OTHER changed the file
1341
1395
transform.create_from_tree(
1342
1396
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))
1397
self.other_tree.id2path(file_id), file_id=file_id,
1398
filter_tree_path=self._get_filter_tree_path(file_id))
1345
1399
return 'done', None
1346
elif merge_hook_params.this_path is not None:
1400
elif self.this_tree.has_id(file_id):
1347
1401
# OTHER deleted the file
1348
1402
return 'delete', None
1350
1404
raise AssertionError(
1351
'winner is OTHER, but file %r not in THIS or OTHER tree'
1352
% (merge_hook_params.base_path,))
1405
'winner is OTHER, but file_id %r not in THIS or OTHER tree'
1354
1408
def merge_contents(self, merge_hook_params):
1355
1409
"""Fallback merge logic after user installed hooks."""
1356
1410
# This function is used in merge hooks as the fallback instance.
1357
# Perhaps making this function and the functions it calls be a
1411
# Perhaps making this function and the functions it calls be a
1358
1412
# a separate class would be better.
1359
1413
if merge_hook_params.winner == 'other':
1360
1414
# OTHER is a straight winner, so replace this contents with other
1364
1418
# BASE is a file, or both converted to files, so at least we
1365
1419
# have agreement that output should be a file.
1367
self.text_merge(merge_hook_params.trans_id,
1368
merge_hook_params.paths)
1421
self.text_merge(merge_hook_params.file_id,
1422
merge_hook_params.trans_id)
1369
1423
except errors.BinaryFile:
1370
1424
return 'not_applicable', None
1371
1425
return 'done', None
1373
1427
return 'not_applicable', None
1375
def get_lines(self, tree, path):
1429
def get_lines(self, tree, file_id):
1376
1430
"""Return the lines in a file, or an empty list."""
1380
kind = tree.kind(path)
1381
except errors.NoSuchFile:
1432
path = tree.id2path(file_id)
1433
except errors.NoSuchId:
1386
return tree.get_file_lines(path)
1436
return tree.get_file_lines(path, file_id)
1388
def text_merge(self, trans_id, paths):
1389
"""Perform a three-way text merge on a file"""
1438
def text_merge(self, file_id, trans_id):
1439
"""Perform a three-way text merge on a file_id"""
1390
1440
# it's possible that we got here with base as a different type.
1391
1441
# 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)
1443
base_path = self.base_tree.id2path(file_id)
1444
except errors.NoSuchId:
1447
if self.base_tree.kind(base_path, file_id) == "file":
1448
base_lines = self.get_lines(self.base_tree, file_id)
1451
other_lines = self.get_lines(self.other_tree, file_id)
1452
this_lines = self.get_lines(self.this_tree, file_id)
1396
1453
m3 = merge3.Merge3(base_lines, this_lines, other_lines,
1397
1454
is_cherrypick=self.cherrypick)
1398
start_marker = b"!START OF MERGE CONFLICT!" + b"I HOPE THIS IS UNIQUE"
1455
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
1399
1456
if self.show_base is True:
1400
base_marker = b'|' * 7
1457
base_marker = '|' * 7
1402
1459
base_marker = None
1404
1461
def iter_merge3(retval):
1405
1462
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",
1463
for line in m3.merge_lines(name_a = "TREE",
1464
name_b = "MERGE-SOURCE",
1465
name_base = "BASE-REVISION",
1409
1466
start_marker=start_marker,
1410
1467
base_marker=base_marker,
1411
1468
reprocess=self.reprocess):
1412
1469
if line.startswith(start_marker):
1413
1470
retval["text_conflicts"] = True
1414
yield line.replace(start_marker, b'<' * 7)
1471
yield line.replace(start_marker, '<' * 7)
1421
1478
self._raw_conflicts.append(('text conflict', trans_id))
1422
1479
name = self.tt.final_name(trans_id)
1423
1480
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,
1481
file_group = self._dump_conflicts(name, parent_id, file_id,
1426
1482
this_lines, base_lines,
1428
1484
file_group.append(trans_id)
1430
def _get_filter_tree_path(self, path):
1487
def _get_filter_tree_path(self, file_id):
1431
1488
if self.this_tree.supports_content_filtering():
1432
1489
# We get the path from the working tree if it exists.
1433
1490
# That fails though when OTHER is adding a file, so
1434
1491
# we fall back to the other tree to find the path if
1435
1492
# 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
1494
return self.this_tree.id2path(file_id)
1495
except errors.NoSuchId:
1496
return self.other_tree.id2path(file_id)
1497
# Skip the id2path lookup for older formats
1444
def _dump_conflicts(self, name, paths, parent_id, file_id, this_lines=None,
1500
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
1445
1501
base_lines=None, other_lines=None, set_version=False,
1446
1502
no_base=False):
1447
1503
"""Emit conflict files.
1449
1505
determined automatically. If set_version is true, the .OTHER, .THIS
1450
1506
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)]
1508
data = [('OTHER', self.other_tree, other_lines),
1509
('THIS', self.this_tree, this_lines)]
1455
1510
if not no_base:
1456
data.append(('BASE', self.base_tree, base_path, base_lines))
1511
data.append(('BASE', self.base_tree, base_lines))
1458
1513
# 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
1514
# ignoring the conflict suffixes
1516
if wt.supports_content_filtering():
1518
filter_tree_path = wt.id2path(file_id)
1519
except errors.NoSuchId:
1520
# file has been deleted
1521
filter_tree_path = None
1462
1523
# Skip the id2path lookup for older formats
1463
1524
filter_tree_path = None
1465
1526
versioned = False
1466
1527
file_group = []
1467
for suffix, tree, path, lines in data:
1468
if path is not None:
1528
for suffix, tree, lines in data:
1530
path = tree.id2path(file_id)
1531
except errors.NoSuchId:
1469
1534
trans_id = self._conflict_file(
1470
name, parent_id, path, tree, suffix, lines,
1535
name, parent_id, path, tree, file_id, suffix, lines,
1472
1537
file_group.append(trans_id)
1473
1538
if set_version and not versioned:
1474
self.tt.version_file(trans_id, file_id=file_id)
1539
self.tt.version_file(file_id, trans_id)
1475
1540
versioned = True
1476
1541
return file_group
1478
def _conflict_file(self, name, parent_id, path, tree, suffix,
1543
def _conflict_file(self, name, parent_id, path, tree, file_id, suffix,
1479
1544
lines=None, filter_tree_path=None):
1480
1545
"""Emit a single conflict file."""
1481
1546
name = name + '.' + suffix
1482
1547
trans_id = self.tt.create_path(name, parent_id)
1483
1548
transform.create_from_tree(
1484
self.tt, trans_id, tree, path,
1486
filter_tree_path=filter_tree_path)
1549
self.tt, trans_id, tree, path,
1550
file_id=file_id, bytes=lines,
1551
filter_tree_path=filter_tree_path)
1487
1552
return trans_id
1489
def _merge_executable(self, paths, trans_id, executable, file_status,
1554
def merge_executable(self, file_id, file_status):
1555
"""Perform a merge on the execute bit."""
1556
executable = [self.executable(t, file_id) for t in (self.base_tree,
1557
self.other_tree, self.this_tree)]
1558
self._merge_executable(file_id, executable, file_status,
1559
resolver=self._three_way)
1561
def _merge_executable(self, file_id, executable, file_status,
1491
1563
"""Perform a merge on the execute bit."""
1492
1564
base_executable, other_executable, this_executable = executable
1493
base_path, other_path, this_path = paths
1494
1565
if file_status == "deleted":
1496
1567
winner = resolver(*executable)
1497
1568
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:
1569
# There must be a None in here, if we have a conflict, but we
1570
# need executability since file status was not deleted.
1571
if self.executable(self.other_tree, file_id) is None:
1501
1572
winner = "this"
1503
1574
winner = "other"
1504
1575
if winner == 'this' and file_status != "modified":
1577
trans_id = self.tt.trans_id_file_id(file_id)
1506
1578
if self.tt.final_kind(trans_id) != "file":
1508
1580
if winner == "this":
1509
1581
executability = this_executable
1511
if other_path is not None:
1583
if self.other_tree.has_id(file_id):
1512
1584
executability = other_executable
1513
elif this_path is not None:
1585
elif self.this_tree.has_id(file_id):
1514
1586
executability = this_executable
1515
elif base_path is not None:
1587
elif self.base_tree_has_id(file_id):
1516
1588
executability = base_executable
1517
1589
if executability is not None:
1590
trans_id = self.tt.trans_id_file_id(file_id)
1518
1591
self.tt.set_executability(executability, trans_id)
1520
1593
def cook_conflicts(self, fs_conflicts):
1526
1599
conflict_type = conflict[0]
1527
1600
if conflict_type == 'path conflict':
1528
1601
(trans_id, file_id,
1529
this_parent, this_name,
1530
other_parent, other_name) = conflict[1:]
1602
this_parent, this_name,
1603
other_parent, other_name) = conflict[1:]
1531
1604
if this_parent is None or this_name is None:
1532
1605
this_path = '<deleted>'
1534
parent_path = fp.get_path(
1607
parent_path = fp.get_path(
1535
1608
self.tt.trans_id_file_id(this_parent))
1536
1609
this_path = osutils.pathjoin(parent_path, this_name)
1537
1610
if other_parent is None or other_name is None:
1538
1611
other_path = '<deleted>'
1540
if other_parent == self.other_tree.path2id(''):
1613
if other_parent == self.other_tree.get_root_id():
1541
1614
# The tree transform doesn't know about the other root,
1542
1615
# so we special case here to avoid a NoFinalPath
1544
1617
parent_path = ''
1546
parent_path = fp.get_path(
1619
parent_path = fp.get_path(
1547
1620
self.tt.trans_id_file_id(other_parent))
1548
1621
other_path = osutils.pathjoin(parent_path, other_name)
1549
1622
c = _mod_conflicts.Conflict.factory(
1652
1722
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,
1724
def _generate_merge_plan(self, file_id, base):
1725
return self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1661
1728
class Diff3Merger(Merge3Merger):
1662
1729
"""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):
1731
def dump_file(self, temp_dir, name, tree, file_id):
1667
1732
out_path = osutils.pathjoin(temp_dir, name)
1668
with open(out_path, "wb") as out_file:
1669
in_file = tree.get_file(path)
1733
out_file = open(out_path, "wb")
1735
in_file = tree.get_file(tree.id2path(file_id), file_id)
1670
1736
for line in in_file:
1671
1737
out_file.write(line)
1672
1740
return out_path
1674
def text_merge(self, trans_id, paths):
1742
def text_merge(self, file_id, trans_id):
1675
1743
"""Perform a diff3 merge using a specified file-id and trans-id.
1676
1744
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
1677
1745
will be dumped, and a will be conflict noted.
1679
1747
import breezy.patch
1680
base_path, other_path, this_path = paths
1681
1748
temp_dir = osutils.mkdtemp(prefix="bzr-")
1683
1750
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)
1751
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1752
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1753
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1690
1754
status = breezy.patch.diff3(new_file, this, base, other)
1691
1755
if status not in (0, 1):
1692
1756
raise errors.BzrError("Unhandled diff3 exit code")
1693
with open(new_file, 'rb') as f:
1757
f = open(new_file, 'rb')
1694
1759
self.tt.create_file(f, trans_id)
1695
1762
if status == 1:
1696
1763
name = self.tt.final_name(trans_id)
1697
1764
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)
1765
self._dump_conflicts(name, parent_id, file_id)
1700
1766
self._raw_conflicts.append(('text conflict', trans_id))
1702
1768
osutils.rmtree(temp_dir)
1812
1878
super(MergeIntoMergeType, self).__init__(*args, **kwargs)
1814
1880
def _compute_transform(self):
1815
with ui.ui_factory.nested_progress_bar() as child_pb:
1881
child_pb = ui.ui_factory.nested_progress_bar()
1816
1883
entries = self._entries_to_incorporate()
1817
1884
entries = list(entries)
1818
for num, (entry, parent_id, relpath) in enumerate(entries):
1819
child_pb.update(gettext('Preparing file merge'),
1885
for num, (entry, parent_id, path) in enumerate(entries):
1886
child_pb.update(gettext('Preparing file merge'), num, len(entries))
1821
1887
parent_trans_id = self.tt.trans_id_file_id(parent_id)
1822
path = osutils.pathjoin(self._source_subpath, relpath)
1823
1888
trans_id = transform.new_by_entry(path, self.tt, entry,
1824
parent_trans_id, self.other_tree)
1889
parent_trans_id, self.other_tree)
1825
1892
self._finish_computing_transform()
1827
1894
def _entries_to_incorporate(self):
1828
1895
"""Yields pairs of (inventory_entry, new_parent)."""
1829
subdir_id = self.other_tree.path2id(self._source_subpath)
1896
other_inv = self.other_tree.root_inventory
1897
subdir_id = other_inv.path2id(self._source_subpath)
1830
1898
if subdir_id is None:
1831
1899
# XXX: The error would be clearer if it gave the URL of the source
1832
1900
# branch, but we don't have a reference to that here.
1833
1901
raise PathNotInTree(self._source_subpath, "Source tree")
1834
subdir = next(self.other_tree.iter_entries_by_dir(
1835
specific_files=[self._source_subpath]))[1]
1902
subdir = other_inv[subdir_id]
1836
1903
parent_in_target = osutils.dirname(self._target_subdir)
1837
1904
target_id = self.this_tree.path2id(parent_in_target)
1838
1905
if target_id is None: