35
41
WorkingTreeNotRevision,
37
44
from bzrlib.merge3 import Merge3
39
45
from bzrlib.osutils import rename, pathjoin
40
from progress import DummyProgress
41
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
42
from bzrlib.symbol_versioning import *
46
from progress import DummyProgress, ProgressPhase
47
from bzrlib.revision import (is_ancestor, NULL_REVISION, ensure_null)
48
from bzrlib.textfile import check_text_lines
43
49
from bzrlib.trace import mutter, warning, note
44
50
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
45
conflicts_strings, FinalPaths, create_by_entry,
51
conflict_pass, FinalPaths, create_by_entry,
52
unique_add, ROOT_PARENT)
53
from bzrlib.versionedfile import PlanWeaveMerge
48
56
# TODO: Report back as changes are merged in
50
def _get_tree(treespec, local_branch=None):
51
location, revno = treespec
52
branch = Branch.open_containing(location)[0]
56
revision = branch.last_revision()
58
revision = branch.get_rev_id(revno)
60
revision = NULL_REVISION
61
return branch, _get_revid_tree(branch, revision, local_branch)
64
def _get_revid_tree(branch, revision, local_branch):
66
base_tree = branch.bzrdir.open_workingtree()
68
if local_branch is not None:
69
if local_branch.base != branch.base:
70
local_branch.fetch(branch, revision)
71
base_tree = local_branch.repository.revision_tree(revision)
73
base_tree = branch.repository.revision_tree(revision)
77
59
def transform_tree(from_tree, to_tree, interesting_ids=None):
78
60
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
82
64
class Merger(object):
83
def __init__(self, this_branch, other_tree=None, base_tree=None,
84
this_tree=None, pb=DummyProgress()):
65
def __init__(self, this_branch, other_tree=None, base_tree=None,
66
this_tree=None, pb=DummyProgress(), change_reporter=None,
85
68
object.__init__(self)
86
69
assert this_tree is not None, "this_tree is required"
87
70
self.this_branch = this_branch
88
self.this_basis = this_branch.last_revision()
71
self.this_basis = _mod_revision.ensure_null(
72
this_branch.last_revision())
89
73
self.this_rev_id = None
90
74
self.this_tree = this_tree
91
75
self.this_revision_tree = None
92
76
self.this_basis_tree = None
93
77
self.other_tree = other_tree
78
self.other_branch = None
94
79
self.base_tree = base_tree
95
80
self.ignore_zero = False
96
81
self.backup_files = False
97
82
self.interesting_ids = None
83
self.interesting_files = None
98
84
self.show_base = False
99
85
self.reprocess = False
102
def revision_tree(self, revision_id):
103
return self.this_branch.repository.revision_tree(revision_id)
88
self.recurse = recurse
89
self.change_reporter = change_reporter
90
self._cached_trees = {}
92
def revision_tree(self, revision_id, branch=None):
93
if revision_id not in self._cached_trees:
95
branch = self.this_branch
97
tree = self.this_tree.revision_tree(revision_id)
98
except errors.NoSuchRevisionInTree:
99
tree = branch.repository.revision_tree(revision_id)
100
self._cached_trees[revision_id] = tree
101
return self._cached_trees[revision_id]
103
def _get_tree(self, treespec, possible_transports=None):
104
from bzrlib import workingtree
105
location, revno = treespec
107
tree = workingtree.WorkingTree.open_containing(location)[0]
108
return tree.branch, tree
109
branch = Branch.open_containing(location, possible_transports)[0]
111
revision_id = branch.last_revision()
113
revision_id = branch.get_rev_id(revno)
114
revision_id = ensure_null(revision_id)
115
return branch, self.revision_tree(revision_id, branch)
105
117
def ensure_revision_trees(self):
106
118
if self.this_revision_tree is None:
107
self.this_basis_tree = self.this_branch.repository.revision_tree(
119
self.this_basis_tree = self.revision_tree(self.this_basis)
109
120
if self.this_basis == self.this_rev_id:
110
121
self.this_revision_tree = self.this_basis_tree
112
123
if self.other_rev_id is None:
113
124
other_basis_tree = self.revision_tree(self.other_basis)
114
changes = compare_trees(self.other_tree, other_basis_tree)
125
changes = other_basis_tree.changes_from(self.other_tree)
115
126
if changes.has_changed():
116
127
raise WorkingTreeNotRevision(self.this_tree)
117
other_rev_id = other_basis
128
other_rev_id = self.other_basis
118
129
self.other_tree = other_basis_tree
120
131
def file_revisions(self, file_id):
131
142
trees = (self.this_basis_tree, self.other_tree)
132
143
return [get_id(tree, file_id) for tree in trees]
134
def check_basis(self, check_clean):
135
if self.this_basis is None:
136
raise BzrCommandError("This branch has no commits")
145
def check_basis(self, check_clean, require_commits=True):
146
if self.this_basis is None and require_commits is True:
147
raise BzrCommandError("This branch has no commits."
148
" (perhaps you would prefer 'bzr pull')")
138
150
self.compare_basis()
139
151
if self.this_basis != self.this_rev_id:
140
152
raise BzrCommandError("Working tree has uncommitted changes.")
142
154
def compare_basis(self):
143
changes = compare_trees(self.this_tree,
144
self.this_tree.basis_tree(), False)
156
basis_tree = self.revision_tree(self.this_tree.last_revision())
157
except errors.RevisionNotPresent:
158
basis_tree = self.this_tree.basis_tree()
159
changes = self.this_tree.changes_from(basis_tree)
145
160
if not changes.has_changed():
146
161
self.this_rev_id = self.this_basis
148
163
def set_interesting_files(self, file_list):
150
self._set_interesting_files(file_list)
151
except NotVersionedError, e:
152
raise BzrCommandError("%s is not a source file in any"
155
def _set_interesting_files(self, file_list):
156
"""Set the list of interesting ids from a list of files."""
157
if file_list is None:
158
self.interesting_ids = None
161
interesting_ids = set()
162
for path in file_list:
164
for tree in (self.this_tree, self.base_tree, self.other_tree):
165
file_id = tree.inventory.path2id(path)
166
if file_id is not None:
167
interesting_ids.add(file_id)
170
raise NotVersionedError(path=path)
171
self.interesting_ids = interesting_ids
164
self.interesting_files = file_list
173
166
def set_pending(self):
174
if not self.base_is_ancestor:
176
if self.other_rev_id is None:
178
ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
179
if self.other_rev_id in ancestry:
181
self.this_tree.add_pending_merge(self.other_rev_id)
183
def set_other(self, other_revision):
184
other_branch, self.other_tree = _get_tree(other_revision,
167
if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
171
def _add_parent(self):
172
new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
173
new_parent_trees = []
174
for revision_id in new_parents:
176
tree = self.revision_tree(revision_id)
177
except errors.RevisionNotPresent:
181
new_parent_trees.append((revision_id, tree))
183
self.this_tree.set_parent_trees(new_parent_trees,
184
allow_leftmost_as_ghost=True)
186
for _revision_id, tree in new_parent_trees:
190
def set_other(self, other_revision, possible_transports=None):
191
"""Set the revision and tree to merge from.
193
This sets the other_tree, other_rev_id, other_basis attributes.
195
:param other_revision: The [path, revision] list to merge from.
197
self.other_branch, self.other_tree = self._get_tree(other_revision,
186
199
if other_revision[1] == -1:
187
self.other_rev_id = other_branch.last_revision()
188
if self.other_rev_id is None:
189
raise NoCommits(other_branch)
200
self.other_rev_id = _mod_revision.ensure_null(
201
self.other_branch.last_revision())
202
if _mod_revision.is_null(self.other_rev_id):
203
raise NoCommits(self.other_branch)
190
204
self.other_basis = self.other_rev_id
191
205
elif other_revision[1] is not None:
192
self.other_rev_id = other_branch.get_rev_id(other_revision[1])
206
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
193
207
self.other_basis = self.other_rev_id
195
209
self.other_rev_id = None
196
self.other_basis = other_branch.last_revision()
210
self.other_basis = self.other_branch.last_revision()
197
211
if self.other_basis is None:
198
raise NoCommits(other_branch)
199
if other_branch.base != self.this_branch.base:
200
self.this_branch.fetch(other_branch, last_revision=self.other_basis)
212
raise NoCommits(self.other_branch)
213
if self.other_rev_id is not None:
214
self._cached_trees[self.other_rev_id] = self.other_tree
215
self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
217
def set_other_revision(self, revision_id, other_branch):
218
"""Set 'other' based on a branch and revision id
220
:param revision_id: The revision to use for a tree
221
:param other_branch: The branch containing this tree
223
self.other_rev_id = revision_id
224
self.other_branch = other_branch
225
self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
226
self.other_tree = self.revision_tree(revision_id)
227
self.other_basis = revision_id
229
def set_base_revision(self, revision_id, branch):
230
"""Set 'base' based on a branch and revision id
232
:param revision_id: The revision to use for a tree
233
:param branch: The branch containing this tree
235
self.base_rev_id = revision_id
236
self.base_branch = branch
237
self._maybe_fetch(branch, self.this_branch, revision_id)
238
self.base_tree = self.revision_tree(revision_id)
239
self.base_is_ancestor = is_ancestor(self.this_basis,
242
self.base_is_other_ancestor = is_ancestor(self.other_basis,
246
def _maybe_fetch(self, source, target, revision_id):
247
if (source.repository.bzrdir.root_transport.base !=
248
target.repository.bzrdir.root_transport.base):
249
target.fetch(source, revision_id)
252
this_repo = self.this_branch.repository
253
graph = this_repo.get_graph()
254
revisions = [ensure_null(self.this_basis),
255
ensure_null(self.other_basis)]
256
if NULL_REVISION in revisions:
257
self.base_rev_id = NULL_REVISION
259
self.base_rev_id = graph.find_unique_lca(*revisions)
260
if self.base_rev_id == NULL_REVISION:
261
raise UnrelatedBranches()
262
self.base_tree = self.revision_tree(self.base_rev_id)
263
self.base_is_ancestor = True
264
self.base_is_other_ancestor = True
202
266
def set_base(self, base_revision):
267
"""Set the base revision to use for the merge.
269
:param base_revision: A 2-list containing a path and revision number.
203
271
mutter("doing merge() with no base_revision specified")
204
272
if base_revision == [None, None]:
206
self.base_rev_id = common_ancestor(self.this_basis,
208
self.this_branch.repository,
210
except NoCommonAncestor:
211
raise UnrelatedBranches()
212
self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
214
self.base_is_ancestor = True
216
base_branch, self.base_tree = _get_tree(base_revision)
275
base_branch, self.base_tree = self._get_tree(base_revision)
217
276
if base_revision[1] == -1:
218
277
self.base_rev_id = base_branch.last_revision()
219
278
elif base_revision[1] is None:
220
self.base_rev_id = None
279
self.base_rev_id = _mod_revision.NULL_REVISION
222
self.base_rev_id = base_branch.get_rev_id(base_revision[1])
223
self.this_branch.fetch(base_branch)
281
self.base_rev_id = _mod_revision.ensure_null(
282
base_branch.get_rev_id(base_revision[1]))
283
self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
224
284
self.base_is_ancestor = is_ancestor(self.this_basis,
225
285
self.base_rev_id,
226
286
self.this_branch)
287
self.base_is_other_ancestor = is_ancestor(self.other_basis,
228
291
def do_merge(self):
229
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
230
'other_tree': self.other_tree}
292
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
293
'other_tree': self.other_tree,
294
'interesting_ids': self.interesting_ids,
295
'interesting_files': self.interesting_files,
231
297
if self.merge_type.requires_base:
232
298
kwargs['base_tree'] = self.base_tree
233
299
if self.merge_type.supports_reprocess:
234
300
kwargs['reprocess'] = self.reprocess
235
301
elif self.reprocess:
236
raise BzrError("Reprocess is not supported for this merge"
237
" type. %s" % merge_type)
302
raise BzrError("Conflict reduction is not supported for merge"
303
" type %s." % self.merge_type)
238
304
if self.merge_type.supports_show_base:
239
305
kwargs['show_base'] = self.show_base
240
306
elif self.show_base:
241
307
raise BzrError("Showing base is not supported for this"
242
308
" merge type. %s" % self.merge_type)
243
merge = self.merge_type(pb=self._pb, **kwargs)
309
self.this_tree.lock_tree_write()
310
if self.base_tree is not None:
311
self.base_tree.lock_read()
312
if self.other_tree is not None:
313
self.other_tree.lock_read()
315
merge = self.merge_type(pb=self._pb,
316
change_reporter=self.change_reporter,
318
if self.recurse == 'down':
319
for path, file_id in self.this_tree.iter_references():
320
sub_tree = self.this_tree.get_nested_tree(file_id, path)
321
other_revision = self.other_tree.get_reference_revision(
323
if other_revision == sub_tree.last_revision():
325
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
326
sub_merge.merge_type = self.merge_type
327
relpath = self.this_tree.relpath(path)
328
other_branch = self.other_branch.reference_parent(file_id, relpath)
329
sub_merge.set_other_revision(other_revision, other_branch)
330
base_revision = self.base_tree.get_reference_revision(file_id)
331
sub_merge.base_tree = \
332
sub_tree.branch.repository.revision_tree(base_revision)
336
if self.other_tree is not None:
337
self.other_tree.unlock()
338
if self.base_tree is not None:
339
self.base_tree.unlock()
340
self.this_tree.unlock()
244
341
if len(merge.cooked_conflicts) == 0:
245
342
if not self.ignore_zero:
246
343
note("All changes applied successfully.")
250
347
return len(merge.cooked_conflicts)
252
def regen_inventory(self, new_entries):
253
old_entries = self.this_tree.read_working_inventory()
257
for path, file_id in new_entries:
260
new_entries_map[file_id] = path
262
def id2path(file_id):
263
path = new_entries_map.get(file_id)
266
entry = old_entries[file_id]
267
if entry.parent_id is None:
269
return pathjoin(id2path(entry.parent_id), entry.name)
271
for file_id in old_entries:
272
entry = old_entries[file_id]
273
path = id2path(file_id)
274
new_inventory[file_id] = (path, file_id, entry.parent_id,
276
by_path[path] = file_id
281
for path, file_id in new_entries:
283
del new_inventory[file_id]
286
new_path_list.append((path, file_id))
287
if file_id not in old_entries:
289
# Ensure no file is added before its parent
291
for path, file_id in new_path_list:
295
parent = by_path[os.path.dirname(path)]
296
abspath = pathjoin(self.this_tree.basedir, path)
297
kind = bzrlib.osutils.file_kind(abspath)
298
new_inventory[file_id] = (path, file_id, parent, kind)
299
by_path[path] = file_id
301
# Get a list in insertion order
302
new_inventory_list = new_inventory.values()
303
mutter ("""Inventory regeneration:
304
old length: %i insertions: %i deletions: %i new_length: %i"""\
305
% (len(old_entries), insertions, deletions,
306
len(new_inventory_list)))
307
assert len(new_inventory_list) == len(old_entries) + insertions\
309
new_inventory_list.sort()
310
return new_inventory_list
313
350
class Merge3Merger(object):
314
351
"""Three-way merger that uses the merge3 text merger"""
316
353
supports_reprocess = True
317
354
supports_show_base = True
318
355
history_based = False
356
winner_idx = {"this": 2, "other": 1, "conflict": 1}
320
358
def __init__(self, working_tree, this_tree, base_tree, other_tree,
321
reprocess=False, show_base=False, pb=DummyProgress()):
322
"""Initialize the merger object and perform the merge."""
359
interesting_ids=None, reprocess=False, show_base=False,
360
pb=DummyProgress(), pp=None, change_reporter=None,
361
interesting_files=None):
362
"""Initialize the merger object and perform the merge.
364
:param working_tree: The working tree to apply the merge to
365
:param this_tree: The local tree in the merge operation
366
:param base_tree: The common tree in the merge operation
367
:param other_tree: The other other tree to merge changes from
368
:param interesting_ids: The file_ids of files that should be
369
participate in the merge. May not be combined with
371
:param: reprocess If True, perform conflict-reduction processing.
372
:param show_base: If True, show the base revision in text conflicts.
373
(incompatible with reprocess)
374
:param pb: A Progress bar
375
:param pp: A ProgressPhase object
376
:param change_reporter: An object that should report changes made
377
:param interesting_files: The tree-relative paths of files that should
378
participate in the merge. If these paths refer to directories,
379
the contents of those directories will also be included. May not
380
be combined with interesting_ids. If neither interesting_files nor
381
interesting_ids is specified, all files may participate in the
323
384
object.__init__(self)
385
if interesting_files is not None:
386
assert interesting_ids is None
387
self.interesting_ids = interesting_ids
388
self.interesting_files = interesting_files
324
389
self.this_tree = working_tree
390
self.this_tree.lock_tree_write()
325
391
self.base_tree = base_tree
392
self.base_tree.lock_read()
326
393
self.other_tree = other_tree
394
self.other_tree.lock_read()
327
395
self._raw_conflicts = []
328
396
self.cooked_conflicts = []
329
397
self.reprocess = reprocess
330
398
self.show_base = show_base
401
self.change_reporter = change_reporter
403
self.pp = ProgressPhase("Merge phase", 3, self.pb)
333
all_ids = set(base_tree)
334
all_ids.update(other_tree)
335
405
self.tt = TreeTransform(working_tree, self.pb)
337
for num, file_id in enumerate(all_ids):
338
self.pb.update('Preparing file merge', num+1, len(all_ids))
339
self.merge_names(file_id)
340
file_status = self.merge_contents(file_id)
341
self.merge_executable(file_id, file_status)
344
fs_conflicts = resolve_conflicts(self.tt, self.pb)
408
entries = self._entries3()
409
child_pb = ui.ui_factory.nested_progress_bar()
411
for num, (file_id, changed, parents3, names3,
412
executable3) in enumerate(entries):
413
child_pb.update('Preparing file merge', num, len(entries))
414
self._merge_names(file_id, parents3, names3)
416
file_status = self.merge_contents(file_id)
418
file_status = 'unmodified'
419
self._merge_executable(file_id,
420
executable3, file_status)
425
child_pb = ui.ui_factory.nested_progress_bar()
427
fs_conflicts = resolve_conflicts(self.tt, child_pb,
428
lambda t, c: conflict_pass(t, c, self.other_tree))
431
if change_reporter is not None:
432
from bzrlib import delta
433
delta.report_changes(self.tt._iter_changes(), change_reporter)
345
434
self.cook_conflicts(fs_conflicts)
346
for line in conflicts_strings(self.cooked_conflicts):
435
for conflict in self.cooked_conflicts:
438
results = self.tt.apply(no_conflicts=True)
439
self.write_modified(results)
441
working_tree.add_conflicts(self.cooked_conflicts)
442
except UnsupportedOperation:
446
self.other_tree.unlock()
447
self.base_tree.unlock()
448
self.this_tree.unlock()
452
"""Gather data about files modified between three trees.
454
Return a list of tuples of file_id, changed, parents3, names3,
455
executable3. changed is a boolean indicating whether the file contents
456
or kind were changed. parents3 is a tuple of parent ids for base,
457
other and this. names3 is a tuple of names for base, other and this.
458
executable3 is a tuple of execute-bit values for base, other and this.
461
iterator = self.other_tree._iter_changes(self.base_tree,
462
include_unchanged=True, specific_files=self.interesting_files,
463
extra_trees=[self.this_tree])
464
for (file_id, paths, changed, versioned, parents, names, kind,
465
executable) in iterator:
466
if (self.interesting_ids is not None and
467
file_id not in self.interesting_ids):
469
if file_id in self.this_tree.inventory:
470
entry = self.this_tree.inventory[file_id]
471
this_name = entry.name
472
this_parent = entry.parent_id
473
this_executable = entry.executable
477
this_executable = None
478
parents3 = parents + (this_parent,)
479
names3 = names + (this_name,)
480
executable3 = executable + (this_executable,)
481
result.append((file_id, changed, parents3, names3, executable3))
486
self.tt.final_kind(self.tt.root)
488
self.tt.cancel_deletion(self.tt.root)
489
if self.tt.final_file_id(self.tt.root) is None:
490
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
492
if self.other_tree.inventory.root is None:
494
other_root_file_id = self.other_tree.inventory.root.file_id
495
other_root = self.tt.trans_id_file_id(other_root_file_id)
496
if other_root == self.tt.root:
499
self.tt.final_kind(other_root)
502
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
503
self.tt.cancel_creation(other_root)
504
self.tt.cancel_versioning(other_root)
506
def reparent_children(self, ie, target):
507
for thing, child in ie.children.iteritems():
508
trans_id = self.tt.trans_id_file_id(child.file_id)
509
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
511
def write_modified(self, results):
513
for path in results.modified_paths:
514
file_id = self.this_tree.path2id(self.this_tree.relpath(path))
517
hash = self.this_tree.get_file_sha1(file_id)
520
modified_hashes[file_id] = hash
521
self.this_tree.set_merge_modified(modified_hashes)
356
524
def parent(entry, file_id):
357
525
"""Determine the parent for a file_id (used as a key method)"""
692
899
this_name = other_name = self.tt.final_name(trans_id)
693
900
other_path = fp.get_path(trans_id)
694
if this_parent is not None:
901
if this_parent is not None and this_name is not None:
695
902
this_parent_path = \
696
903
fp.get_path(self.tt.trans_id_file_id(this_parent))
697
904
this_path = pathjoin(this_parent_path, this_name)
699
906
this_path = "<deleted>"
700
907
file_id = self.tt.final_file_id(trans_id)
701
self.cooked_conflicts.append(('path conflict', file_id, this_path,
908
c = Conflict.factory('path conflict', path=this_path,
909
conflict_path=other_path, file_id=file_id)
910
self.cooked_conflicts.append(c)
911
self.cooked_conflicts.sort(key=Conflict.sort_key)
705
914
class WeaveMerger(Merge3Merger):
706
915
"""Three-way tree merger, text weave merger."""
707
supports_reprocess = False
916
supports_reprocess = True
708
917
supports_show_base = False
710
919
def __init__(self, working_tree, this_tree, base_tree, other_tree,
712
self.this_revision_tree = self._get_revision_tree(this_tree)
713
self.other_revision_tree = self._get_revision_tree(other_tree)
920
interesting_ids=None, pb=DummyProgress(), pp=None,
921
reprocess=False, change_reporter=None,
922
interesting_files=None):
714
923
super(WeaveMerger, self).__init__(working_tree, this_tree,
715
base_tree, other_tree, pb=pb)
717
def _get_revision_tree(self, tree):
718
"""Return a revision tree releated to this tree.
719
If the tree is a WorkingTree, the basis will be returned.
721
if getattr(tree, 'get_weave', False) is False:
722
# If we have a WorkingTree, try using the basis
723
return tree.branch.basis_tree()
727
def _check_file(self, file_id):
728
"""Check that the revision tree's version of the file matches."""
729
for tree, rt in ((self.this_tree, self.this_revision_tree),
730
(self.other_tree, self.other_revision_tree)):
733
if tree.get_file_sha1(file_id) != rt.get_file_sha1(file_id):
734
raise WorkingTreeNotRevision(self.this_tree)
924
base_tree, other_tree,
925
interesting_ids=interesting_ids,
926
pb=pb, pp=pp, reprocess=reprocess,
927
change_reporter=change_reporter)
736
929
def _merged_lines(self, file_id):
737
930
"""Generate the merged lines.
738
931
There is no distinction between lines that are meant to contain <<<<<<<
741
weave = self.this_revision_tree.get_weave(file_id)
742
this_revision_id = self.this_revision_tree.inventory[file_id].revision
743
other_revision_id = \
744
self.other_revision_tree.inventory[file_id].revision
745
this_i = weave.lookup(this_revision_id)
746
other_i = weave.lookup(other_revision_id)
747
plan = weave.plan_merge(this_i, other_i)
748
return weave.weave_merge(plan)
934
plan = self.this_tree.plan_file_merge(file_id, self.other_tree)
935
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
936
'>>>>>>> MERGE-SOURCE\n')
937
return textmerge.merge_lines(self.reprocess)
750
939
def text_merge(self, file_id, trans_id):
751
940
"""Perform a (weave) text merge for a given file and file-id.
752
941
If conflicts are encountered, .THIS and .OTHER files will be emitted,
753
942
and a conflict will be noted.
755
self._check_file(file_id)
756
lines = self._merged_lines(file_id)
757
conflicts = '<<<<<<<\n' in lines
944
lines, conflicts = self._merged_lines(file_id)
946
# Note we're checking whether the OUTPUT is binary in this case,
947
# because we don't want to get into weave merge guts.
948
check_text_lines(lines)
758
949
self.tt.create_file(lines, trans_id)
760
951
self._raw_conflicts.append(('text conflict', trans_id))
817
1017
branch.get_revision_tree(base_revision))'
819
1019
if this_tree is None:
820
warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
821
"bzrlib version 0.8.",
824
this_tree = this_branch.bzrdir.open_workingtree()
825
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1020
raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1021
"parameter as of bzrlib version 0.8.")
1022
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1023
pb=pb, change_reporter=change_reporter)
827
1024
merger.backup_files = backup_files
828
1025
merger.merge_type = merge_type
829
1026
merger.interesting_ids = interesting_ids
1027
merger.ignore_zero = ignore_zero
830
1028
if interesting_files:
831
1029
assert not interesting_ids, ('Only supply interesting_ids'
832
1030
' or interesting_files')
833
merger._set_interesting_files(interesting_files)
834
merger.show_base = show_base
1031
merger.interesting_files = interesting_files
1032
merger.show_base = show_base
835
1033
merger.reprocess = reprocess
836
1034
merger.other_rev_id = other_rev_id
837
1035
merger.other_basis = other_rev_id
838
1036
return merger.do_merge()
841
merge_types = { "merge3": (Merge3Merger, "Native diff3-style merge"),
842
"diff3": (Diff3Merger, "Merge using external diff3"),
843
'weave': (WeaveMerger, "Weave-based merge")
1038
def get_merge_type_registry():
1039
"""Merge type registry is in bzrlib.option to avoid circular imports.
1041
This method provides a sanctioned way to retrieve it.
1043
from bzrlib import option
1044
return option._merge_type_registry
1047
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
1048
def status_a(revision, text):
1049
if revision in ancestors_b:
1050
return 'killed-b', text
1052
return 'new-a', text
1054
def status_b(revision, text):
1055
if revision in ancestors_a:
1056
return 'killed-a', text
1058
return 'new-b', text
1060
plain_a = [t for (a, t) in annotated_a]
1061
plain_b = [t for (a, t) in annotated_b]
1062
matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
1063
blocks = matcher.get_matching_blocks()
1066
for ai, bi, l in blocks:
1067
# process all mismatched sections
1068
# (last mismatched section is handled because blocks always
1069
# includes a 0-length last block)
1070
for revision, text in annotated_a[a_cur:ai]:
1071
yield status_a(revision, text)
1072
for revision, text in annotated_b[b_cur:bi]:
1073
yield status_b(revision, text)
1075
# and now the matched section
1078
for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
1079
assert text_a == text_b
1080
yield "unchanged", text_a