88
89
self.change_reporter = change_reporter
89
90
self._cached_trees = {}
93
def from_uncommitted(tree, other_tree, pb):
94
"""Return a Merger for uncommitted changes in other_tree.
96
:param tree: The tree to merge into
97
:param other_tree: The tree to get uncommitted changes from
98
:param pb: A progress indicator
100
merger = Merger(tree.branch, other_tree, other_tree.basis_tree(), tree,
102
merger.base_rev_id = merger.base_tree.get_revision_id()
103
merger.other_rev_id = None
107
def from_mergeable(klass, tree, mergeable, pb):
108
"""Return a Merger for a bundle or merge directive.
110
:param tree: The tree to merge changes into
111
:param mergeable: A merge directive or bundle
112
:param pb: A progress indicator
114
mergeable.install_revisions(tree.branch.repository)
115
base_revision_id, other_revision_id, verified =\
116
mergeable.get_merge_request(tree.branch.repository)
117
if (base_revision_id != _mod_revision.NULL_REVISION and
118
tree.branch.repository.get_graph().is_ancestor(
119
base_revision_id, tree.branch.last_revision())):
120
base_revision_id = None
121
merger = klass.from_revision_ids(pb, tree, other_revision_id,
123
return merger, verified
126
def from_revision_ids(pb, this, other, base=None, other_branch=None,
128
"""Return a Merger for revision-ids.
130
:param tree: The tree to merge changes into
131
:param other: The revision-id to use as OTHER
132
:param base: The revision-id to use as BASE. If not specified, will
134
:param other_branch: A branch containing the other revision-id. If
135
not supplied, this.branch is used.
136
:param base_branch: A branch containing the base revision-id. If
137
not supplied, other_branch or this.branch will be used.
138
:param pb: A progress indicator
140
merger = Merger(this.branch, this_tree=this, pb=pb)
141
if other_branch is None:
142
other_branch = this.branch
143
merger.set_other_revision(other, other_branch)
147
if base_branch is None:
148
base_branch = other_branch
149
merger.set_base_revision(base, base_branch)
91
152
def revision_tree(self, revision_id, branch=None):
92
153
if revision_id not in self._cached_trees:
93
154
if branch is None:
99
160
self._cached_trees[revision_id] = tree
100
161
return self._cached_trees[revision_id]
102
def _get_tree(self, treespec):
163
def _get_tree(self, treespec, possible_transports=None):
103
164
from bzrlib import workingtree
104
165
location, revno = treespec
105
166
if revno is None:
106
167
tree = workingtree.WorkingTree.open_containing(location)[0]
107
168
return tree.branch, tree
108
branch = Branch.open_containing(location)[0]
169
branch = Branch.open_containing(location, possible_transports)[0]
110
171
revision_id = branch.last_revision()
186
247
if tree is not None:
189
def set_other(self, other_revision):
250
def set_other(self, other_revision, possible_transports=None):
190
251
"""Set the revision and tree to merge from.
192
253
This sets the other_tree, other_rev_id, other_basis attributes.
194
255
:param other_revision: The [path, revision] list to merge from.
196
self.other_branch, self.other_tree = self._get_tree(other_revision)
257
self.other_branch, self.other_tree = self._get_tree(other_revision,
197
259
if other_revision[1] == -1:
198
260
self.other_rev_id = _mod_revision.ensure_null(
199
261
self.other_branch.last_revision())
224
286
self.other_tree = self.revision_tree(revision_id)
225
287
self.other_basis = revision_id
289
def set_base_revision(self, revision_id, branch):
290
"""Set 'base' based on a branch and revision id
292
:param revision_id: The revision to use for a tree
293
:param branch: The branch containing this tree
295
self.base_rev_id = revision_id
296
self.base_branch = branch
297
self._maybe_fetch(branch, self.this_branch, revision_id)
298
self.base_tree = self.revision_tree(revision_id)
299
self.base_is_ancestor = is_ancestor(self.this_basis,
302
self.base_is_other_ancestor = is_ancestor(self.other_basis,
227
306
def _maybe_fetch(self, source, target, revision_id):
228
307
if (source.repository.bzrdir.root_transport.base !=
229
308
target.repository.bzrdir.root_transport.base):
901
980
interesting_ids=None, pb=DummyProgress(), pp=None,
902
981
reprocess=False, change_reporter=None,
903
982
interesting_files=None):
904
self.this_revision_tree = self._get_revision_tree(this_tree)
905
self.other_revision_tree = self._get_revision_tree(other_tree)
906
983
super(WeaveMerger, self).__init__(working_tree, this_tree,
907
984
base_tree, other_tree,
908
985
interesting_ids=interesting_ids,
909
986
pb=pb, pp=pp, reprocess=reprocess,
910
987
change_reporter=change_reporter)
912
def _get_revision_tree(self, tree):
913
"""Return a revision tree related to this tree.
914
If the tree is a WorkingTree, the basis will be returned.
916
if getattr(tree, 'get_weave', False) is False:
917
# If we have a WorkingTree, try using the basis
918
return tree.branch.basis_tree()
922
def _check_file(self, file_id):
923
"""Check that the revision tree's version of the file matches."""
924
for tree, rt in ((self.this_tree, self.this_revision_tree),
925
(self.other_tree, self.other_revision_tree)):
928
if tree.get_file_sha1(file_id) != rt.get_file_sha1(file_id):
929
raise WorkingTreeNotRevision(self.this_tree)
931
989
def _merged_lines(self, file_id):
932
990
"""Generate the merged lines.
933
991
There is no distinction between lines that are meant to contain <<<<<<<
936
weave = self.this_revision_tree.get_weave(file_id)
937
this_revision_id = self.this_revision_tree.inventory[file_id].revision
938
other_revision_id = \
939
self.other_revision_tree.inventory[file_id].revision
940
wm = WeaveMerge(weave, this_revision_id, other_revision_id,
941
'<<<<<<< TREE\n', '>>>>>>> MERGE-SOURCE\n')
942
return wm.merge_lines(self.reprocess)
994
plan = self.this_tree.plan_file_merge(file_id, self.other_tree)
995
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
996
'>>>>>>> MERGE-SOURCE\n')
997
return textmerge.merge_lines(self.reprocess)
944
999
def text_merge(self, file_id, trans_id):
945
1000
"""Perform a (weave) text merge for a given file and file-id.
946
1001
If conflicts are encountered, .THIS and .OTHER files will be emitted,
947
1002
and a conflict will be noted.
949
self._check_file(file_id)
950
1004
lines, conflicts = self._merged_lines(file_id)
951
1005
lines = list(lines)
952
1006
# Note we're checking whether the OUTPUT is binary in this case,
1049
1103
from bzrlib import option
1050
1104
return option._merge_type_registry
1107
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
1108
def status_a(revision, text):
1109
if revision in ancestors_b:
1110
return 'killed-b', text
1112
return 'new-a', text
1114
def status_b(revision, text):
1115
if revision in ancestors_a:
1116
return 'killed-a', text
1118
return 'new-b', text
1120
plain_a = [t for (a, t) in annotated_a]
1121
plain_b = [t for (a, t) in annotated_b]
1122
matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
1123
blocks = matcher.get_matching_blocks()
1126
for ai, bi, l in blocks:
1127
# process all mismatched sections
1128
# (last mismatched section is handled because blocks always
1129
# includes a 0-length last block)
1130
for revision, text in annotated_a[a_cur:ai]:
1131
yield status_a(revision, text)
1132
for revision, text in annotated_b[b_cur:bi]:
1133
yield status_b(revision, text)
1135
# and now the matched section
1138
for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
1139
assert text_a == text_b
1140
yield "unchanged", text_a