85
70
class Merger(object):
86
def __init__(self, this_branch, other_tree=None, base_tree=None,
87
this_tree=None, pb=DummyProgress()):
71
def __init__(self, this_branch, other_tree=None, base_tree=None,
72
this_tree=None, pb=DummyProgress(), change_reporter=None,
73
recurse='down', revision_graph=None):
88
74
object.__init__(self)
89
assert this_tree is not None, "this_tree is required"
90
75
self.this_branch = this_branch
91
self.this_basis = this_branch.last_revision()
76
self.this_basis = _mod_revision.ensure_null(
77
this_branch.last_revision())
92
78
self.this_rev_id = None
93
79
self.this_tree = this_tree
94
80
self.this_revision_tree = None
95
81
self.this_basis_tree = None
96
82
self.other_tree = other_tree
83
self.other_branch = None
97
84
self.base_tree = base_tree
98
85
self.ignore_zero = False
99
86
self.backup_files = False
100
87
self.interesting_ids = None
88
self.interesting_files = None
101
89
self.show_base = False
102
90
self.reprocess = False
107
def revision_tree(self, revision_id):
108
return self.this_branch.repository.revision_tree(revision_id)
93
self.recurse = recurse
94
self.change_reporter = change_reporter
95
self._cached_trees = {}
96
self._revision_graph = revision_graph
97
self._base_is_ancestor = None
98
self._base_is_other_ancestor = None
101
def revision_graph(self):
102
if self._revision_graph is None:
103
self._revision_graph = self.this_branch.repository.get_graph()
104
return self._revision_graph
106
def _set_base_is_ancestor(self, value):
107
self._base_is_ancestor = value
109
def _get_base_is_ancestor(self):
110
if self._base_is_ancestor is None:
111
self._base_is_ancestor = self.revision_graph.is_ancestor(
112
self.base_rev_id, self.this_basis)
113
return self._base_is_ancestor
115
base_is_ancestor = property(_get_base_is_ancestor, _set_base_is_ancestor)
117
def _set_base_is_other_ancestor(self, value):
118
self._base_is_other_ancestor = value
120
def _get_base_is_other_ancestor(self):
121
if self._base_is_other_ancestor is None:
122
if self.other_basis is None:
124
self._base_is_other_ancestor = self.revision_graph.is_ancestor(
125
self.base_rev_id, self.other_basis)
126
return self._base_is_other_ancestor
128
base_is_other_ancestor = property(_get_base_is_other_ancestor,
129
_set_base_is_other_ancestor)
132
def from_uncommitted(tree, other_tree, pb):
133
"""Return a Merger for uncommitted changes in other_tree.
135
:param tree: The tree to merge into
136
:param other_tree: The tree to get uncommitted changes from
137
:param pb: A progress indicator
139
merger = Merger(tree.branch, other_tree, other_tree.basis_tree(), tree,
141
merger.base_rev_id = merger.base_tree.get_revision_id()
142
merger.other_rev_id = None
143
merger.other_basis = merger.base_rev_id
147
def from_mergeable(klass, tree, mergeable, pb):
148
"""Return a Merger for a bundle or merge directive.
150
:param tree: The tree to merge changes into
151
:param mergeable: A merge directive or bundle
152
:param pb: A progress indicator
154
mergeable.install_revisions(tree.branch.repository)
155
base_revision_id, other_revision_id, verified =\
156
mergeable.get_merge_request(tree.branch.repository)
157
revision_graph = tree.branch.repository.get_graph()
158
if base_revision_id is not None:
159
if (base_revision_id != _mod_revision.NULL_REVISION and
160
revision_graph.is_ancestor(
161
base_revision_id, tree.branch.last_revision())):
162
base_revision_id = None
164
warning('Performing cherrypick')
165
merger = klass.from_revision_ids(pb, tree, other_revision_id,
166
base_revision_id, revision_graph=
168
return merger, verified
171
def from_revision_ids(pb, tree, other, base=None, other_branch=None,
172
base_branch=None, revision_graph=None):
173
"""Return a Merger for revision-ids.
175
:param tree: The tree to merge changes into
176
:param other: The revision-id to use as OTHER
177
:param base: The revision-id to use as BASE. If not specified, will
179
:param other_branch: A branch containing the other revision-id. If
180
not supplied, tree.branch is used.
181
:param base_branch: A branch containing the base revision-id. If
182
not supplied, other_branch or tree.branch will be used.
183
:param revision_graph: If you have a revision_graph precomputed, pass
184
it in, otherwise it will be created for you.
185
:param pb: A progress indicator
187
merger = Merger(tree.branch, this_tree=tree, pb=pb,
188
revision_graph=revision_graph)
189
if other_branch is None:
190
other_branch = tree.branch
191
merger.set_other_revision(other, other_branch)
195
if base_branch is None:
196
base_branch = other_branch
197
merger.set_base_revision(base, base_branch)
200
def revision_tree(self, revision_id, branch=None):
201
if revision_id not in self._cached_trees:
203
branch = self.this_branch
205
tree = self.this_tree.revision_tree(revision_id)
206
except errors.NoSuchRevisionInTree:
207
tree = branch.repository.revision_tree(revision_id)
208
self._cached_trees[revision_id] = tree
209
return self._cached_trees[revision_id]
211
def _get_tree(self, treespec, possible_transports=None):
212
from bzrlib import workingtree
213
location, revno = treespec
215
tree = workingtree.WorkingTree.open_containing(location)[0]
216
return tree.branch, tree
217
branch = Branch.open_containing(location, possible_transports)[0]
219
revision_id = branch.last_revision()
221
revision_id = branch.get_rev_id(revno)
222
revision_id = ensure_null(revision_id)
223
return branch, self.revision_tree(revision_id, branch)
110
225
def ensure_revision_trees(self):
111
226
if self.this_revision_tree is None:
112
self.this_basis_tree = self.this_branch.repository.revision_tree(
227
self.this_basis_tree = self.revision_tree(self.this_basis)
114
228
if self.this_basis == self.this_rev_id:
115
229
self.this_revision_tree = self.this_basis_tree
117
231
if self.other_rev_id is None:
118
232
other_basis_tree = self.revision_tree(self.other_basis)
119
changes = compare_trees(self.other_tree, other_basis_tree)
233
changes = other_basis_tree.changes_from(self.other_tree)
120
234
if changes.has_changed():
121
235
raise WorkingTreeNotRevision(self.this_tree)
122
236
other_rev_id = self.other_basis
139
252
def check_basis(self, check_clean, require_commits=True):
140
253
if self.this_basis is None and require_commits is True:
141
raise BzrCommandError("This branch has no commits")
254
raise BzrCommandError("This branch has no commits."
255
" (perhaps you would prefer 'bzr pull')")
143
257
self.compare_basis()
144
258
if self.this_basis != self.this_rev_id:
145
raise BzrCommandError("Working tree has uncommitted changes.")
259
raise errors.UncommittedChanges(self.this_tree)
147
261
def compare_basis(self):
148
changes = compare_trees(self.this_tree,
149
self.this_tree.basis_tree(), False)
263
basis_tree = self.revision_tree(self.this_tree.last_revision())
264
except errors.NoSuchRevision:
265
basis_tree = self.this_tree.basis_tree()
266
changes = self.this_tree.changes_from(basis_tree)
150
267
if not changes.has_changed():
151
268
self.this_rev_id = self.this_basis
153
270
def set_interesting_files(self, file_list):
155
self._set_interesting_files(file_list)
156
except NotVersionedError, e:
157
raise BzrCommandError("%s is not a source file in any"
160
def _set_interesting_files(self, file_list):
161
"""Set the list of interesting ids from a list of files."""
162
if file_list is None:
163
self.interesting_ids = None
166
interesting_ids = set()
167
for path in file_list:
169
for tree in (self.this_tree, self.base_tree, self.other_tree):
170
file_id = tree.inventory.path2id(path)
171
if file_id is not None:
172
interesting_ids.add(file_id)
175
raise NotVersionedError(path=path)
176
self.interesting_ids = interesting_ids
271
self.interesting_files = file_list
178
273
def set_pending(self):
179
if not self.base_is_ancestor:
181
if self.other_rev_id is None:
183
ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
184
if self.other_rev_id in ancestry:
186
self.this_tree.add_pending_merge(self.other_rev_id)
188
def set_other(self, other_revision):
189
other_branch, self.other_tree = _get_tree(other_revision,
274
if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
278
def _add_parent(self):
279
new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
280
new_parent_trees = []
281
for revision_id in new_parents:
283
tree = self.revision_tree(revision_id)
284
except errors.NoSuchRevision:
288
new_parent_trees.append((revision_id, tree))
290
self.this_tree.set_parent_trees(new_parent_trees,
291
allow_leftmost_as_ghost=True)
293
for _revision_id, tree in new_parent_trees:
297
def set_other(self, other_revision, possible_transports=None):
298
"""Set the revision and tree to merge from.
300
This sets the other_tree, other_rev_id, other_basis attributes.
302
:param other_revision: The [path, revision] list to merge from.
304
self.other_branch, self.other_tree = self._get_tree(other_revision,
191
306
if other_revision[1] == -1:
192
self.other_rev_id = other_branch.last_revision()
193
if self.other_rev_id is None:
194
raise NoCommits(other_branch)
307
self.other_rev_id = _mod_revision.ensure_null(
308
self.other_branch.last_revision())
309
if _mod_revision.is_null(self.other_rev_id):
310
raise NoCommits(self.other_branch)
195
311
self.other_basis = self.other_rev_id
196
312
elif other_revision[1] is not None:
197
self.other_rev_id = other_branch.get_rev_id(other_revision[1])
313
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
198
314
self.other_basis = self.other_rev_id
200
316
self.other_rev_id = None
201
self.other_basis = other_branch.last_revision()
317
self.other_basis = self.other_branch.last_revision()
202
318
if self.other_basis is None:
203
raise NoCommits(other_branch)
204
if other_branch.base != self.this_branch.base:
205
self.this_branch.fetch(other_branch, last_revision=self.other_basis)
319
raise NoCommits(self.other_branch)
320
if self.other_rev_id is not None:
321
self._cached_trees[self.other_rev_id] = self.other_tree
322
self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
324
def set_other_revision(self, revision_id, other_branch):
325
"""Set 'other' based on a branch and revision id
327
:param revision_id: The revision to use for a tree
328
:param other_branch: The branch containing this tree
330
self.other_rev_id = revision_id
331
self.other_branch = other_branch
332
self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
333
self.other_tree = self.revision_tree(revision_id)
334
self.other_basis = revision_id
336
def set_base_revision(self, revision_id, branch):
337
"""Set 'base' based on a branch and revision id
339
:param revision_id: The revision to use for a tree
340
:param branch: The branch containing this tree
342
self.base_rev_id = revision_id
343
self.base_branch = branch
344
self._maybe_fetch(branch, self.this_branch, revision_id)
345
self.base_tree = self.revision_tree(revision_id)
347
def _maybe_fetch(self, source, target, revision_id):
348
if not source.repository.has_same_location(target.repository):
349
target.fetch(source, revision_id)
207
351
def find_base(self):
208
self.set_base([None, None])
352
revisions = [ensure_null(self.this_basis),
353
ensure_null(self.other_basis)]
354
if NULL_REVISION in revisions:
355
self.base_rev_id = NULL_REVISION
357
self.base_rev_id, steps = self.revision_graph.find_unique_lca(
358
revisions[0], revisions[1], count_steps=True)
359
if self.base_rev_id == NULL_REVISION:
360
raise UnrelatedBranches()
362
warning('Warning: criss-cross merge encountered. See bzr'
363
' help criss-cross.')
364
self.base_tree = self.revision_tree(self.base_rev_id)
365
self.base_is_ancestor = True
366
self.base_is_other_ancestor = True
210
368
def set_base(self, base_revision):
369
"""Set the base revision to use for the merge.
371
:param base_revision: A 2-list containing a path and revision number.
211
373
mutter("doing merge() with no base_revision specified")
212
374
if base_revision == [None, None]:
214
pb = bzrlib.ui.ui_factory.nested_progress_bar()
216
this_repo = self.this_branch.repository
217
self.base_rev_id = common_ancestor(self.this_basis,
222
except NoCommonAncestor:
223
raise UnrelatedBranches()
224
self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
226
self.base_is_ancestor = True
228
base_branch, self.base_tree = _get_tree(base_revision)
377
base_branch, self.base_tree = self._get_tree(base_revision)
229
378
if base_revision[1] == -1:
230
379
self.base_rev_id = base_branch.last_revision()
231
380
elif base_revision[1] is None:
232
self.base_rev_id = None
381
self.base_rev_id = _mod_revision.NULL_REVISION
234
self.base_rev_id = base_branch.get_rev_id(base_revision[1])
235
if self.this_branch.base != base_branch.base:
236
self.this_branch.fetch(base_branch)
237
self.base_is_ancestor = is_ancestor(self.this_basis,
383
self.base_rev_id = _mod_revision.ensure_null(
384
base_branch.get_rev_id(base_revision[1]))
385
self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
242
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
243
'other_tree': self.other_tree,
387
def make_merger(self):
388
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
389
'other_tree': self.other_tree,
244
390
'interesting_ids': self.interesting_ids,
391
'interesting_files': self.interesting_files,
246
394
if self.merge_type.requires_base:
247
395
kwargs['base_tree'] = self.base_tree
248
396
if self.merge_type.supports_reprocess:
254
402
kwargs['show_base'] = self.show_base
255
403
elif self.show_base:
256
404
raise BzrError("Showing base is not supported for this"
257
" merge type. %s" % self.merge_type)
258
merge = self.merge_type(pb=self._pb, **kwargs)
405
" merge type. %s" % self.merge_type)
406
if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
407
and not self.base_is_other_ancestor):
408
raise errors.CannotReverseCherrypick()
409
if self.merge_type.supports_cherrypick:
410
kwargs['cherrypick'] = (not self.base_is_ancestor or
411
not self.base_is_other_ancestor)
412
return self.merge_type(pb=self._pb,
413
change_reporter=self.change_reporter,
417
self.this_tree.lock_tree_write()
418
if self.base_tree is not None:
419
self.base_tree.lock_read()
420
if self.other_tree is not None:
421
self.other_tree.lock_read()
423
merge = self.make_merger()
425
if self.recurse == 'down':
426
for relpath, file_id in self.this_tree.iter_references():
427
sub_tree = self.this_tree.get_nested_tree(file_id, relpath)
428
other_revision = self.other_tree.get_reference_revision(
430
if other_revision == sub_tree.last_revision():
432
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
433
sub_merge.merge_type = self.merge_type
434
other_branch = self.other_branch.reference_parent(file_id, relpath)
435
sub_merge.set_other_revision(other_revision, other_branch)
436
base_revision = self.base_tree.get_reference_revision(file_id)
437
sub_merge.base_tree = \
438
sub_tree.branch.repository.revision_tree(base_revision)
439
sub_merge.base_rev_id = base_revision
443
if self.other_tree is not None:
444
self.other_tree.unlock()
445
if self.base_tree is not None:
446
self.base_tree.unlock()
447
self.this_tree.unlock()
259
448
if len(merge.cooked_conflicts) == 0:
260
if not self.ignore_zero:
449
if not self.ignore_zero and not is_quiet():
261
450
note("All changes applied successfully.")
263
452
note("%d conflicts encountered." % len(merge.cooked_conflicts))
265
454
return len(merge.cooked_conflicts)
267
def regen_inventory(self, new_entries):
268
old_entries = self.this_tree.read_working_inventory()
272
for path, file_id in new_entries:
275
new_entries_map[file_id] = path
277
def id2path(file_id):
278
path = new_entries_map.get(file_id)
281
entry = old_entries[file_id]
282
if entry.parent_id is None:
284
return pathjoin(id2path(entry.parent_id), entry.name)
286
for file_id in old_entries:
287
entry = old_entries[file_id]
288
path = id2path(file_id)
289
if file_id in self.base_tree.inventory:
290
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
292
executable = getattr(entry, 'executable', False)
293
new_inventory[file_id] = (path, file_id, entry.parent_id,
294
entry.kind, executable)
296
by_path[path] = file_id
301
for path, file_id in new_entries:
303
del new_inventory[file_id]
306
new_path_list.append((path, file_id))
307
if file_id not in old_entries:
309
# Ensure no file is added before its parent
311
for path, file_id in new_path_list:
315
parent = by_path[os.path.dirname(path)]
316
abspath = pathjoin(self.this_tree.basedir, path)
317
kind = bzrlib.osutils.file_kind(abspath)
318
if file_id in self.base_tree.inventory:
319
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
322
new_inventory[file_id] = (path, file_id, parent, kind, executable)
323
by_path[path] = file_id
325
# Get a list in insertion order
326
new_inventory_list = new_inventory.values()
327
mutter ("""Inventory regeneration:
328
old length: %i insertions: %i deletions: %i new_length: %i"""\
329
% (len(old_entries), insertions, deletions,
330
len(new_inventory_list)))
331
assert len(new_inventory_list) == len(old_entries) + insertions\
333
new_inventory_list.sort()
334
return new_inventory_list
337
457
class Merge3Merger(object):
338
458
"""Three-way merger that uses the merge3 text merger"""
355
506
self.show_base = show_base
509
self.change_reporter = change_reporter
510
self.cherrypick = cherrypick
358
511
if self.pp is None:
359
512
self.pp = ProgressPhase("Merge phase", 3, self.pb)
361
if interesting_ids is not None:
362
all_ids = interesting_ids
364
all_ids = set(base_tree)
365
all_ids.update(other_tree)
366
working_tree.lock_write()
367
self.tt = TreeTransform(working_tree, self.pb)
517
self.this_tree.lock_tree_write()
518
self.base_tree.lock_read()
519
self.other_tree.lock_read()
520
self.tt = TreeTransform(self.this_tree, self.pb)
369
522
self.pp.next_phase()
370
child_pb = ui.ui_factory.nested_progress_bar()
372
for num, file_id in enumerate(all_ids):
373
child_pb.update('Preparing file merge', num, len(all_ids))
374
self.merge_names(file_id)
375
file_status = self.merge_contents(file_id)
376
self.merge_executable(file_id, file_status)
381
child_pb = ui.ui_factory.nested_progress_bar()
383
fs_conflicts = resolve_conflicts(self.tt, child_pb)
386
self.cook_conflicts(fs_conflicts)
387
for conflict in self.cooked_conflicts:
390
results = self.tt.apply()
523
self._compute_transform()
525
results = self.tt.apply(no_conflicts=True)
391
526
self.write_modified(results)
393
working_tree.add_conflicts(self.cooked_conflicts)
528
self.this_tree.add_conflicts(self.cooked_conflicts)
394
529
except UnsupportedOperation:
397
532
self.tt.finalize()
398
working_tree.unlock()
533
self.other_tree.unlock()
534
self.base_tree.unlock()
535
self.this_tree.unlock()
538
def make_preview_transform(self):
539
self.base_tree.lock_read()
540
self.other_tree.lock_read()
541
self.tt = TransformPreview(self.this_tree)
544
self._compute_transform()
547
self.other_tree.unlock()
548
self.base_tree.unlock()
552
def _compute_transform(self):
553
entries = self._entries3()
554
child_pb = ui.ui_factory.nested_progress_bar()
556
for num, (file_id, changed, parents3, names3,
557
executable3) in enumerate(entries):
558
child_pb.update('Preparing file merge', num, len(entries))
559
self._merge_names(file_id, parents3, names3)
561
file_status = self.merge_contents(file_id)
563
file_status = 'unmodified'
564
self._merge_executable(file_id,
565
executable3, file_status)
570
child_pb = ui.ui_factory.nested_progress_bar()
572
fs_conflicts = resolve_conflicts(self.tt, child_pb,
573
lambda t, c: conflict_pass(t, c, self.other_tree))
576
if self.change_reporter is not None:
577
from bzrlib import delta
578
delta.report_changes(
579
self.tt.iter_changes(), self.change_reporter)
580
self.cook_conflicts(fs_conflicts)
581
for conflict in self.cooked_conflicts:
585
"""Gather data about files modified between three trees.
587
Return a list of tuples of file_id, changed, parents3, names3,
588
executable3. changed is a boolean indicating whether the file contents
589
or kind were changed. parents3 is a tuple of parent ids for base,
590
other and this. names3 is a tuple of names for base, other and this.
591
executable3 is a tuple of execute-bit values for base, other and this.
594
iterator = self.other_tree.iter_changes(self.base_tree,
595
include_unchanged=True, specific_files=self.interesting_files,
596
extra_trees=[self.this_tree])
597
for (file_id, paths, changed, versioned, parents, names, kind,
598
executable) in iterator:
599
if (self.interesting_ids is not None and
600
file_id not in self.interesting_ids):
602
if file_id in self.this_tree.inventory:
603
entry = self.this_tree.inventory[file_id]
604
this_name = entry.name
605
this_parent = entry.parent_id
606
this_executable = entry.executable
610
this_executable = None
611
parents3 = parents + (this_parent,)
612
names3 = names + (this_name,)
613
executable3 = executable + (this_executable,)
614
result.append((file_id, changed, parents3, names3, executable3))
619
self.tt.final_kind(self.tt.root)
621
self.tt.cancel_deletion(self.tt.root)
622
if self.tt.final_file_id(self.tt.root) is None:
623
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
625
if self.other_tree.inventory.root is None:
627
other_root_file_id = self.other_tree.get_root_id()
628
other_root = self.tt.trans_id_file_id(other_root_file_id)
629
if other_root == self.tt.root:
632
self.tt.final_kind(other_root)
635
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
636
self.tt.cancel_creation(other_root)
637
self.tt.cancel_versioning(other_root)
639
def reparent_children(self, ie, target):
640
for thing, child in ie.children.iteritems():
641
trans_id = self.tt.trans_id_file_id(child.file_id)
642
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
401
644
def write_modified(self, results):
402
645
modified_hashes = {}
900
1176
branch.get_revision_tree(base_revision))'
902
1178
if this_tree is None:
903
warnings.warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
904
"bzrlib version 0.8.",
907
this_tree = this_branch.bzrdir.open_workingtree()
908
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1179
raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1180
"parameter as of bzrlib version 0.8.")
1181
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1182
pb=pb, change_reporter=change_reporter)
910
1183
merger.backup_files = backup_files
911
1184
merger.merge_type = merge_type
912
1185
merger.interesting_ids = interesting_ids
913
1186
merger.ignore_zero = ignore_zero
914
1187
if interesting_files:
915
assert not interesting_ids, ('Only supply interesting_ids'
916
' or interesting_files')
917
merger._set_interesting_files(interesting_files)
918
merger.show_base = show_base
1189
raise ValueError('Only supply interesting_ids'
1190
' or interesting_files')
1191
merger.interesting_files = interesting_files
1192
merger.show_base = show_base
919
1193
merger.reprocess = reprocess
920
1194
merger.other_rev_id = other_rev_id
921
1195
merger.other_basis = other_rev_id
1196
get_revision_id = getattr(base_tree, 'get_revision_id', None)
1197
if get_revision_id is None:
1198
get_revision_id = base_tree.last_revision
1199
merger.set_base_revision(get_revision_id(), this_branch)
922
1200
return merger.do_merge()
925
merge_types = { "merge3": (Merge3Merger, "Native diff3-style merge"),
926
"diff3": (Diff3Merger, "Merge using external diff3"),
927
'weave': (WeaveMerger, "Weave-based merge")
931
def merge_type_help():
932
templ = '%s%%7s: %%s' % (' '*12)
933
lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
934
return '\n'.join(lines)
1202
def get_merge_type_registry():
1203
"""Merge type registry is in bzrlib.option to avoid circular imports.
1205
This method provides a sanctioned way to retrieve it.
1207
from bzrlib import option
1208
return option._merge_type_registry
1211
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
1212
def status_a(revision, text):
1213
if revision in ancestors_b:
1214
return 'killed-b', text
1216
return 'new-a', text
1218
def status_b(revision, text):
1219
if revision in ancestors_a:
1220
return 'killed-a', text
1222
return 'new-b', text
1224
plain_a = [t for (a, t) in annotated_a]
1225
plain_b = [t for (a, t) in annotated_b]
1226
matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
1227
blocks = matcher.get_matching_blocks()
1230
for ai, bi, l in blocks:
1231
# process all mismatched sections
1232
# (last mismatched section is handled because blocks always
1233
# includes a 0-length last block)
1234
for revision, text in annotated_a[a_cur:ai]:
1235
yield status_a(revision, text)
1236
for revision, text in annotated_b[b_cur:bi]:
1237
yield status_b(revision, text)
1238
# and now the matched section
1241
for text_a in plain_a[ai:a_cur]:
1242
yield "unchanged", text_a
1245
class _PlanMergeBase(object):
1247
def __init__(self, a_rev, b_rev, vf, key_prefix):
1250
:param a_rev: Revision-id of one revision to merge
1251
:param b_rev: Revision-id of the other revision to merge
1252
:param vf: A VersionedFiles containing both revisions
1253
:param key_prefix: A prefix for accessing keys in vf, typically
1259
self._last_lines = None
1260
self._last_lines_revision_id = None
1261
self._cached_matching_blocks = {}
1262
self._key_prefix = key_prefix
1263
self._precache_tip_lines()
1265
def _precache_tip_lines(self):
1266
lines = self.get_lines([self.a_rev, self.b_rev])
1267
self.lines_a = lines[self.a_rev]
1268
self.lines_b = lines[self.b_rev]
1270
def get_lines(self, revisions):
1271
"""Get lines for revisions from the backing VersionedFiles.
1273
:raises RevisionNotPresent: on absent texts.
1275
keys = [(self._key_prefix + (rev,)) for rev in revisions]
1277
for record in self.vf.get_record_stream(keys, 'unordered', True):
1278
if record.storage_kind == 'absent':
1279
raise errors.RevisionNotPresent(record.key, self.vf)
1280
result[record.key[-1]] = osutils.split_lines(
1281
record.get_bytes_as('fulltext'))
1284
def plan_merge(self):
1285
"""Generate a 'plan' for merging the two revisions.
1287
This involves comparing their texts and determining the cause of
1288
differences. If text A has a line and text B does not, then either the
1289
line was added to text A, or it was deleted from B. Once the causes
1290
are combined, they are written out in the format described in
1291
VersionedFile.plan_merge
1293
blocks = self._get_matching_blocks(self.a_rev, self.b_rev)
1294
unique_a, unique_b = self._unique_lines(blocks)
1295
new_a, killed_b = self._determine_status(self.a_rev, unique_a)
1296
new_b, killed_a = self._determine_status(self.b_rev, unique_b)
1297
return self._iter_plan(blocks, new_a, killed_b, new_b, killed_a)
1299
def _iter_plan(self, blocks, new_a, killed_b, new_b, killed_a):
1302
for i, j, n in blocks:
1303
for a_index in range(last_i, i):
1304
if a_index in new_a:
1305
if a_index in killed_b:
1306
yield 'conflicted-a', self.lines_a[a_index]
1308
yield 'new-a', self.lines_a[a_index]
1310
yield 'killed-b', self.lines_a[a_index]
1311
for b_index in range(last_j, j):
1312
if b_index in new_b:
1313
if b_index in killed_a:
1314
yield 'conflicted-b', self.lines_b[b_index]
1316
yield 'new-b', self.lines_b[b_index]
1318
yield 'killed-a', self.lines_b[b_index]
1319
# handle common lines
1320
for a_index in range(i, i+n):
1321
yield 'unchanged', self.lines_a[a_index]
1325
def _get_matching_blocks(self, left_revision, right_revision):
1326
"""Return a description of which sections of two revisions match.
1328
See SequenceMatcher.get_matching_blocks
1330
cached = self._cached_matching_blocks.get((left_revision,
1332
if cached is not None:
1334
if self._last_lines_revision_id == left_revision:
1335
left_lines = self._last_lines
1336
right_lines = self.get_lines([right_revision])[right_revision]
1338
lines = self.get_lines([left_revision, right_revision])
1339
left_lines = lines[left_revision]
1340
right_lines = lines[right_revision]
1341
self._last_lines = right_lines
1342
self._last_lines_revision_id = right_revision
1343
matcher = patiencediff.PatienceSequenceMatcher(None, left_lines,
1345
return matcher.get_matching_blocks()
1347
def _unique_lines(self, matching_blocks):
1348
"""Analyse matching_blocks to determine which lines are unique
1350
:return: a tuple of (unique_left, unique_right), where the values are
1351
sets of line numbers of unique lines.
1357
for i, j, n in matching_blocks:
1358
unique_left.extend(range(last_i, i))
1359
unique_right.extend(range(last_j, j))
1362
return unique_left, unique_right
1365
def _subtract_plans(old_plan, new_plan):
1366
"""Remove changes from new_plan that came from old_plan.
1368
It is assumed that the difference between the old_plan and new_plan
1369
is their choice of 'b' text.
1371
All lines from new_plan that differ from old_plan are emitted
1372
verbatim. All lines from new_plan that match old_plan but are
1373
not about the 'b' revision are emitted verbatim.
1375
Lines that match and are about the 'b' revision are the lines we
1376
don't want, so we convert 'killed-b' -> 'unchanged', and 'new-b'
1377
is skipped entirely.
1379
matcher = patiencediff.PatienceSequenceMatcher(None, old_plan,
1382
for i, j, n in matcher.get_matching_blocks():
1383
for jj in range(last_j, j):
1385
for jj in range(j, j+n):
1386
plan_line = new_plan[jj]
1387
if plan_line[0] == 'new-b':
1389
elif plan_line[0] == 'killed-b':
1390
yield 'unchanged', plan_line[1]
1396
class _PlanMerge(_PlanMergeBase):
1397
"""Plan an annotate merge using on-the-fly annotation"""
1399
def __init__(self, a_rev, b_rev, vf, key_prefix):
1400
super(_PlanMerge, self).__init__(a_rev, b_rev, vf, key_prefix)
1401
self.a_key = self._key_prefix + (self.a_rev,)
1402
self.b_key = self._key_prefix + (self.b_rev,)
1403
self.graph = Graph(self.vf)
1404
heads = self.graph.heads((self.a_key, self.b_key))
1406
# one side dominates, so we can just return its values, yay for
1408
# Ideally we would know that before we get this far
1409
self._head_key = heads.pop()
1410
if self._head_key == self.a_key:
1414
mutter('found dominating revision for %s\n%s > %s', self.vf,
1415
self._head_key[-1], other)
1418
self._head_key = None
1421
def _precache_tip_lines(self):
1422
# Turn this into a no-op, because we will do this later
1425
def _find_recursive_lcas(self):
1426
"""Find all the ancestors back to a unique lca"""
1427
cur_ancestors = (self.a_key, self.b_key)
1428
# graph.find_lca(uncommon, keys) now returns plain NULL_REVISION,
1429
# rather than a key tuple. We will just map that directly to no common
1433
next_lcas = self.graph.find_lca(*cur_ancestors)
1434
# Map a plain NULL_REVISION to a simple no-ancestors
1435
if next_lcas == set([NULL_REVISION]):
1437
# Order the lca's based on when they were merged into the tip
1438
# While the actual merge portion of weave merge uses a set() of
1439
# active revisions, the order of insertion *does* effect the
1440
# implicit ordering of the texts.
1441
for rev_key in cur_ancestors:
1442
ordered_parents = tuple(self.graph.find_merge_order(rev_key,
1444
parent_map[rev_key] = ordered_parents
1445
if len(next_lcas) == 0:
1447
elif len(next_lcas) == 1:
1448
parent_map[list(next_lcas)[0]] = ()
1450
elif len(next_lcas) > 2:
1451
# More than 2 lca's, fall back to grabbing all nodes between
1452
# this and the unique lca.
1453
mutter('More than 2 LCAs, falling back to all nodes for:'
1454
' %s, %s\n=> %s', self.a_key, self.b_key, cur_ancestors)
1455
cur_lcas = next_lcas
1456
while len(cur_lcas) > 1:
1457
cur_lcas = self.graph.find_lca(*cur_lcas)
1458
if len(cur_lcas) == 0:
1459
# No common base to find, use the full ancestry
1462
unique_lca = list(cur_lcas)[0]
1463
if unique_lca == NULL_REVISION:
1464
# find_lca will return a plain 'NULL_REVISION' rather
1465
# than a key tuple when there is no common ancestor, we
1466
# prefer to just use None, because it doesn't confuse
1467
# _get_interesting_texts()
1469
parent_map.update(self._find_unique_parents(next_lcas,
1472
cur_ancestors = next_lcas
1475
def _find_unique_parents(self, tip_keys, base_key):
1476
"""Find ancestors of tip that aren't ancestors of base.
1478
:param tip_keys: Nodes that are interesting
1479
:param base_key: Cull all ancestors of this node
1480
:return: The parent map for all revisions between tip_keys and
1481
base_key. base_key will be included. References to nodes outside of
1482
the ancestor set will also be removed.
1484
# TODO: this would be simpler if find_unique_ancestors took a list
1485
# instead of a single tip, internally it supports it, but it
1486
# isn't a "backwards compatible" api change.
1487
if base_key is None:
1488
parent_map = dict(self.graph.iter_ancestry(tip_keys))
1489
# We remove NULL_REVISION because it isn't a proper tuple key, and
1490
# thus confuses things like _get_interesting_texts, and our logic
1491
# to add the texts into the memory weave.
1492
if NULL_REVISION in parent_map:
1493
parent_map.pop(NULL_REVISION)
1496
for tip in tip_keys:
1498
self.graph.find_unique_ancestors(tip, [base_key]))
1499
parent_map = self.graph.get_parent_map(interesting)
1500
parent_map[base_key] = ()
1501
culled_parent_map, child_map, tails = self._remove_external_references(
1503
# Remove all the tails but base_key
1504
if base_key is not None:
1505
tails.remove(base_key)
1506
self._prune_tails(culled_parent_map, child_map, tails)
1507
# Now remove all the uninteresting 'linear' regions
1508
simple_map = _mod_graph.collapse_linear_regions(culled_parent_map)
1512
def _remove_external_references(parent_map):
1513
"""Remove references that go outside of the parent map.
1515
:param parent_map: Something returned from Graph.get_parent_map(keys)
1516
:return: (filtered_parent_map, child_map, tails)
1517
filtered_parent_map is parent_map without external references
1518
child_map is the {parent_key: [child_keys]} mapping
1519
tails is a list of nodes that do not have any parents in the map
1521
# TODO: The basic effect of this function seems more generic than
1522
# _PlanMerge. But the specific details of building a child_map,
1523
# and computing tails seems very specific to _PlanMerge.
1524
# Still, should this be in Graph land?
1525
filtered_parent_map = {}
1528
for key, parent_keys in parent_map.iteritems():
1529
culled_parent_keys = [p for p in parent_keys if p in parent_map]
1530
if not culled_parent_keys:
1532
for parent_key in culled_parent_keys:
1533
child_map.setdefault(parent_key, []).append(key)
1534
# TODO: Do we want to do this, it adds overhead for every node,
1535
# just to say that the node has no children
1536
child_map.setdefault(key, [])
1537
filtered_parent_map[key] = culled_parent_keys
1538
return filtered_parent_map, child_map, tails
1541
def _prune_tails(parent_map, child_map, tails_to_remove):
1542
"""Remove tails from the parent map.
1544
This will remove the supplied revisions until no more children have 0
1547
:param parent_map: A dict of {child: [parents]}, this dictionary will
1548
be modified in place.
1549
:param tails_to_remove: A list of tips that should be removed,
1550
this list will be consumed
1551
:param child_map: The reverse dict of parent_map ({parent: [children]})
1552
this dict will be modified
1553
:return: None, parent_map will be modified in place.
1555
while tails_to_remove:
1556
next = tails_to_remove.pop()
1557
parent_map.pop(next)
1558
children = child_map.pop(next)
1559
for child in children:
1560
child_parents = parent_map[child]
1561
child_parents.remove(next)
1562
if len(child_parents) == 0:
1563
tails_to_remove.append(child)
1565
def _get_interesting_texts(self, parent_map):
1566
"""Return a dict of texts we are interested in.
1568
Note that the input is in key tuples, but the output is in plain
1571
:param parent_map: The output from _find_recursive_lcas
1572
:return: A dict of {'revision_id':lines} as returned by
1573
_PlanMergeBase.get_lines()
1575
all_revision_keys = set(parent_map)
1576
all_revision_keys.add(self.a_key)
1577
all_revision_keys.add(self.b_key)
1579
# Everything else is in 'keys' but get_lines is in 'revision_ids'
1580
all_texts = self.get_lines([k[-1] for k in all_revision_keys])
1583
def _build_weave(self):
1584
from bzrlib import weave
1585
self._weave = weave.Weave(weave_name='in_memory_weave',
1586
allow_reserved=True)
1587
parent_map = self._find_recursive_lcas()
1589
all_texts = self._get_interesting_texts(parent_map)
1591
# Note: Unfortunately, the order given by topo_sort will effect the
1592
# ordering resolution in the output. Specifically, if you add A then B,
1593
# then in the output text A lines will show up before B lines. And, of
1594
# course, topo_sort doesn't guarantee any real ordering.
1595
# So we use merge_sort, and add a fake node on the tip.
1596
# This ensures that left-hand parents will always be inserted into the
1597
# weave before right-hand parents.
1598
tip_key = self._key_prefix + (_mod_revision.CURRENT_REVISION,)
1599
parent_map[tip_key] = (self.a_key, self.b_key)
1601
for seq_num, key, depth, eom in reversed(tsort.merge_sort(parent_map,
1605
# for key in tsort.topo_sort(parent_map):
1606
parent_keys = parent_map[key]
1607
revision_id = key[-1]
1608
parent_ids = [k[-1] for k in parent_keys]
1609
self._weave.add_lines(revision_id, parent_ids,
1610
all_texts[revision_id])
1612
def plan_merge(self):
1613
"""Generate a 'plan' for merging the two revisions.
1615
This involves comparing their texts and determining the cause of
1616
differences. If text A has a line and text B does not, then either the
1617
line was added to text A, or it was deleted from B. Once the causes
1618
are combined, they are written out in the format described in
1619
VersionedFile.plan_merge
1621
if self._head_key is not None: # There was a single head
1622
if self._head_key == self.a_key:
1625
if self._head_key != self.b_key:
1626
raise AssertionError('There was an invalid head: %s != %s'
1627
% (self.b_key, self._head_key))
1629
head_rev = self._head_key[-1]
1630
lines = self.get_lines([head_rev])[head_rev]
1631
return ((plan, line) for line in lines)
1632
return self._weave.plan_merge(self.a_rev, self.b_rev)
1635
class _PlanLCAMerge(_PlanMergeBase):
1637
This merge algorithm differs from _PlanMerge in that:
1638
1. comparisons are done against LCAs only
1639
2. cases where a contested line is new versus one LCA but old versus
1640
another are marked as conflicts, by emitting the line as conflicted-a
1643
This is faster, and hopefully produces more useful output.
1646
def __init__(self, a_rev, b_rev, vf, key_prefix, graph):
1647
_PlanMergeBase.__init__(self, a_rev, b_rev, vf, key_prefix)
1648
lcas = graph.find_lca(key_prefix + (a_rev,), key_prefix + (b_rev,))
1651
if lca == NULL_REVISION:
1654
self.lcas.add(lca[-1])
1655
for lca in self.lcas:
1656
if _mod_revision.is_null(lca):
1659
lca_lines = self.get_lines([lca])[lca]
1660
matcher = patiencediff.PatienceSequenceMatcher(None, self.lines_a,
1662
blocks = list(matcher.get_matching_blocks())
1663
self._cached_matching_blocks[(a_rev, lca)] = blocks
1664
matcher = patiencediff.PatienceSequenceMatcher(None, self.lines_b,
1666
blocks = list(matcher.get_matching_blocks())
1667
self._cached_matching_blocks[(b_rev, lca)] = blocks
1669
def _determine_status(self, revision_id, unique_line_numbers):
1670
"""Determines the status unique lines versus all lcas.
1672
Basically, determines why the line is unique to this revision.
1674
A line may be determined new, killed, or both.
1676
If a line is determined new, that means it was not present in at least
1677
one LCA, and is not present in the other merge revision.
1679
If a line is determined killed, that means the line was present in
1682
If a line is killed and new, this indicates that the two merge
1683
revisions contain differing conflict resolutions.
1684
:param revision_id: The id of the revision in which the lines are
1686
:param unique_line_numbers: The line numbers of unique lines.
1687
:return a tuple of (new_this, killed_other):
1691
unique_line_numbers = set(unique_line_numbers)
1692
for lca in self.lcas:
1693
blocks = self._get_matching_blocks(revision_id, lca)
1694
unique_vs_lca, _ignored = self._unique_lines(blocks)
1695
new.update(unique_line_numbers.intersection(unique_vs_lca))
1696
killed.update(unique_line_numbers.difference(unique_vs_lca))