1
# Copyright (C) 2005, 2006 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27
revision as _mod_revision,
29
from bzrlib.branch import Branch
30
from bzrlib.conflicts import ConflictList, Conflict
31
from bzrlib.errors import (BzrCommandError,
41
WorkingTreeNotRevision,
44
from bzrlib.merge3 import Merge3
45
from bzrlib.osutils import rename, pathjoin
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
49
from bzrlib.trace import mutter, warning, note
50
from bzrlib.transform import (TransformPreview, TreeTransform,
51
resolve_conflicts, cook_conflicts,
52
conflict_pass, FinalPaths, create_by_entry,
53
unique_add, ROOT_PARENT)
54
from bzrlib.versionedfile import PlanWeaveMerge
57
# TODO: Report back as changes are merged in
60
def transform_tree(from_tree, to_tree, interesting_ids=None):
61
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
62
interesting_ids=interesting_ids, this_tree=from_tree)
66
def __init__(self, this_branch, other_tree=None, base_tree=None,
67
this_tree=None, pb=DummyProgress(), change_reporter=None,
70
assert this_tree is not None, "this_tree is required"
71
self.this_branch = this_branch
72
self.this_basis = _mod_revision.ensure_null(
73
this_branch.last_revision())
74
self.this_rev_id = None
75
self.this_tree = this_tree
76
self.this_revision_tree = None
77
self.this_basis_tree = None
78
self.other_tree = other_tree
79
self.other_branch = None
80
self.base_tree = base_tree
81
self.ignore_zero = False
82
self.backup_files = False
83
self.interesting_ids = None
84
self.interesting_files = None
85
self.show_base = False
86
self.reprocess = False
89
self.recurse = recurse
90
self.change_reporter = change_reporter
91
self._cached_trees = {}
94
def from_uncommitted(tree, other_tree, pb):
95
"""Return a Merger for uncommitted changes in other_tree.
97
:param tree: The tree to merge into
98
:param other_tree: The tree to get uncommitted changes from
99
:param pb: A progress indicator
101
merger = Merger(tree.branch, other_tree, other_tree.basis_tree(), tree,
103
merger.base_rev_id = merger.base_tree.get_revision_id()
104
merger.other_rev_id = None
108
def from_mergeable(klass, tree, mergeable, pb):
109
"""Return a Merger for a bundle or merge directive.
111
:param tree: The tree to merge changes into
112
:param mergeable: A merge directive or bundle
113
:param pb: A progress indicator
115
mergeable.install_revisions(tree.branch.repository)
116
base_revision_id, other_revision_id, verified =\
117
mergeable.get_merge_request(tree.branch.repository)
118
if (base_revision_id != _mod_revision.NULL_REVISION and
119
tree.branch.repository.get_graph().is_ancestor(
120
base_revision_id, tree.branch.last_revision())):
121
base_revision_id = None
122
merger = klass.from_revision_ids(pb, tree, other_revision_id,
124
return merger, verified
127
def from_revision_ids(pb, this, other, base=None, other_branch=None,
129
"""Return a Merger for revision-ids.
131
:param tree: The tree to merge changes into
132
:param other: The revision-id to use as OTHER
133
:param base: The revision-id to use as BASE. If not specified, will
135
:param other_branch: A branch containing the other revision-id. If
136
not supplied, this.branch is used.
137
:param base_branch: A branch containing the base revision-id. If
138
not supplied, other_branch or this.branch will be used.
139
:param pb: A progress indicator
141
merger = Merger(this.branch, this_tree=this, pb=pb)
142
if other_branch is None:
143
other_branch = this.branch
144
merger.set_other_revision(other, other_branch)
148
if base_branch is None:
149
base_branch = other_branch
150
merger.set_base_revision(base, base_branch)
153
def revision_tree(self, revision_id, branch=None):
154
if revision_id not in self._cached_trees:
156
branch = self.this_branch
158
tree = self.this_tree.revision_tree(revision_id)
159
except errors.NoSuchRevisionInTree:
160
tree = branch.repository.revision_tree(revision_id)
161
self._cached_trees[revision_id] = tree
162
return self._cached_trees[revision_id]
164
def _get_tree(self, treespec, possible_transports=None):
165
from bzrlib import workingtree
166
location, revno = treespec
168
tree = workingtree.WorkingTree.open_containing(location)[0]
169
return tree.branch, tree
170
branch = Branch.open_containing(location, possible_transports)[0]
172
revision_id = branch.last_revision()
174
revision_id = branch.get_rev_id(revno)
175
revision_id = ensure_null(revision_id)
176
return branch, self.revision_tree(revision_id, branch)
178
def ensure_revision_trees(self):
179
if self.this_revision_tree is None:
180
self.this_basis_tree = self.revision_tree(self.this_basis)
181
if self.this_basis == self.this_rev_id:
182
self.this_revision_tree = self.this_basis_tree
184
if self.other_rev_id is None:
185
other_basis_tree = self.revision_tree(self.other_basis)
186
changes = other_basis_tree.changes_from(self.other_tree)
187
if changes.has_changed():
188
raise WorkingTreeNotRevision(self.this_tree)
189
other_rev_id = self.other_basis
190
self.other_tree = other_basis_tree
192
def file_revisions(self, file_id):
193
self.ensure_revision_trees()
194
def get_id(tree, file_id):
195
revision_id = tree.inventory[file_id].revision
196
assert revision_id is not None
198
if self.this_rev_id is None:
199
if self.this_basis_tree.get_file_sha1(file_id) != \
200
self.this_tree.get_file_sha1(file_id):
201
raise WorkingTreeNotRevision(self.this_tree)
203
trees = (self.this_basis_tree, self.other_tree)
204
return [get_id(tree, file_id) for tree in trees]
206
def check_basis(self, check_clean, require_commits=True):
207
if self.this_basis is None and require_commits is True:
208
raise BzrCommandError("This branch has no commits."
209
" (perhaps you would prefer 'bzr pull')")
212
if self.this_basis != self.this_rev_id:
213
raise errors.UncommittedChanges(self.this_tree)
215
def compare_basis(self):
217
basis_tree = self.revision_tree(self.this_tree.last_revision())
218
except errors.RevisionNotPresent:
219
basis_tree = self.this_tree.basis_tree()
220
changes = self.this_tree.changes_from(basis_tree)
221
if not changes.has_changed():
222
self.this_rev_id = self.this_basis
224
def set_interesting_files(self, file_list):
225
self.interesting_files = file_list
227
def set_pending(self):
228
if not self.base_is_ancestor or not self.base_is_other_ancestor or self.other_rev_id is None:
232
def _add_parent(self):
233
new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
234
new_parent_trees = []
235
for revision_id in new_parents:
237
tree = self.revision_tree(revision_id)
238
except errors.RevisionNotPresent:
242
new_parent_trees.append((revision_id, tree))
244
self.this_tree.set_parent_trees(new_parent_trees,
245
allow_leftmost_as_ghost=True)
247
for _revision_id, tree in new_parent_trees:
251
def set_other(self, other_revision, possible_transports=None):
252
"""Set the revision and tree to merge from.
254
This sets the other_tree, other_rev_id, other_basis attributes.
256
:param other_revision: The [path, revision] list to merge from.
258
self.other_branch, self.other_tree = self._get_tree(other_revision,
260
if other_revision[1] == -1:
261
self.other_rev_id = _mod_revision.ensure_null(
262
self.other_branch.last_revision())
263
if _mod_revision.is_null(self.other_rev_id):
264
raise NoCommits(self.other_branch)
265
self.other_basis = self.other_rev_id
266
elif other_revision[1] is not None:
267
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
268
self.other_basis = self.other_rev_id
270
self.other_rev_id = None
271
self.other_basis = self.other_branch.last_revision()
272
if self.other_basis is None:
273
raise NoCommits(self.other_branch)
274
if self.other_rev_id is not None:
275
self._cached_trees[self.other_rev_id] = self.other_tree
276
self._maybe_fetch(self.other_branch,self.this_branch, self.other_basis)
278
def set_other_revision(self, revision_id, other_branch):
279
"""Set 'other' based on a branch and revision id
281
:param revision_id: The revision to use for a tree
282
:param other_branch: The branch containing this tree
284
self.other_rev_id = revision_id
285
self.other_branch = other_branch
286
self._maybe_fetch(other_branch, self.this_branch, self.other_rev_id)
287
self.other_tree = self.revision_tree(revision_id)
288
self.other_basis = revision_id
290
def set_base_revision(self, revision_id, branch):
291
"""Set 'base' based on a branch and revision id
293
:param revision_id: The revision to use for a tree
294
:param branch: The branch containing this tree
296
self.base_rev_id = revision_id
297
self.base_branch = branch
298
self._maybe_fetch(branch, self.this_branch, revision_id)
299
self.base_tree = self.revision_tree(revision_id)
300
self.base_is_ancestor = is_ancestor(self.this_basis,
303
self.base_is_other_ancestor = is_ancestor(self.other_basis,
307
def _maybe_fetch(self, source, target, revision_id):
308
if not source.repository.has_same_location(target.repository):
309
target.fetch(source, revision_id)
312
this_repo = self.this_branch.repository
313
graph = this_repo.get_graph()
314
revisions = [ensure_null(self.this_basis),
315
ensure_null(self.other_basis)]
316
if NULL_REVISION in revisions:
317
self.base_rev_id = NULL_REVISION
319
self.base_rev_id = graph.find_unique_lca(*revisions)
320
if self.base_rev_id == NULL_REVISION:
321
raise UnrelatedBranches()
322
self.base_tree = self.revision_tree(self.base_rev_id)
323
self.base_is_ancestor = True
324
self.base_is_other_ancestor = True
326
def set_base(self, base_revision):
327
"""Set the base revision to use for the merge.
329
:param base_revision: A 2-list containing a path and revision number.
331
mutter("doing merge() with no base_revision specified")
332
if base_revision == [None, None]:
335
base_branch, self.base_tree = self._get_tree(base_revision)
336
if base_revision[1] == -1:
337
self.base_rev_id = base_branch.last_revision()
338
elif base_revision[1] is None:
339
self.base_rev_id = _mod_revision.NULL_REVISION
341
self.base_rev_id = _mod_revision.ensure_null(
342
base_branch.get_rev_id(base_revision[1]))
343
self._maybe_fetch(base_branch, self.this_branch, self.base_rev_id)
344
self.base_is_ancestor = is_ancestor(self.this_basis,
347
self.base_is_other_ancestor = is_ancestor(self.other_basis,
351
def make_merger(self):
352
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
353
'other_tree': self.other_tree,
354
'interesting_ids': self.interesting_ids,
355
'interesting_files': self.interesting_files,
358
if self.merge_type.requires_base:
359
kwargs['base_tree'] = self.base_tree
360
if self.merge_type.supports_reprocess:
361
kwargs['reprocess'] = self.reprocess
363
raise BzrError("Conflict reduction is not supported for merge"
364
" type %s." % self.merge_type)
365
if self.merge_type.supports_show_base:
366
kwargs['show_base'] = self.show_base
368
raise BzrError("Showing base is not supported for this"
369
" merge type. %s" % self.merge_type)
370
return self.merge_type(pb=self._pb,
371
change_reporter=self.change_reporter,
375
merge = self.make_merger()
376
self.this_tree.lock_tree_write()
377
if self.base_tree is not None:
378
self.base_tree.lock_read()
379
if self.other_tree is not None:
380
self.other_tree.lock_read()
383
if self.recurse == 'down':
384
for path, file_id in self.this_tree.iter_references():
385
sub_tree = self.this_tree.get_nested_tree(file_id, path)
386
other_revision = self.other_tree.get_reference_revision(
388
if other_revision == sub_tree.last_revision():
390
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
391
sub_merge.merge_type = self.merge_type
392
relpath = self.this_tree.relpath(path)
393
other_branch = self.other_branch.reference_parent(file_id, relpath)
394
sub_merge.set_other_revision(other_revision, other_branch)
395
base_revision = self.base_tree.get_reference_revision(file_id)
396
sub_merge.base_tree = \
397
sub_tree.branch.repository.revision_tree(base_revision)
401
if self.other_tree is not None:
402
self.other_tree.unlock()
403
if self.base_tree is not None:
404
self.base_tree.unlock()
405
self.this_tree.unlock()
406
if len(merge.cooked_conflicts) == 0:
407
if not self.ignore_zero:
408
note("All changes applied successfully.")
410
note("%d conflicts encountered." % len(merge.cooked_conflicts))
412
return len(merge.cooked_conflicts)
415
class Merge3Merger(object):
416
"""Three-way merger that uses the merge3 text merger"""
418
supports_reprocess = True
419
supports_show_base = True
420
history_based = False
421
winner_idx = {"this": 2, "other": 1, "conflict": 1}
423
def __init__(self, working_tree, this_tree, base_tree, other_tree,
424
interesting_ids=None, reprocess=False, show_base=False,
425
pb=DummyProgress(), pp=None, change_reporter=None,
426
interesting_files=None, do_merge=True):
427
"""Initialize the merger object and perform the merge.
429
:param working_tree: The working tree to apply the merge to
430
:param this_tree: The local tree in the merge operation
431
:param base_tree: The common tree in the merge operation
432
:param other_tree: The other other tree to merge changes from
433
:param interesting_ids: The file_ids of files that should be
434
participate in the merge. May not be combined with
436
:param: reprocess If True, perform conflict-reduction processing.
437
:param show_base: If True, show the base revision in text conflicts.
438
(incompatible with reprocess)
439
:param pb: A Progress bar
440
:param pp: A ProgressPhase object
441
:param change_reporter: An object that should report changes made
442
:param interesting_files: The tree-relative paths of files that should
443
participate in the merge. If these paths refer to directories,
444
the contents of those directories will also be included. May not
445
be combined with interesting_ids. If neither interesting_files nor
446
interesting_ids is specified, all files may participate in the
449
object.__init__(self)
450
if interesting_files is not None:
451
assert interesting_ids is None
452
self.interesting_ids = interesting_ids
453
self.interesting_files = interesting_files
454
self.this_tree = working_tree
455
self.base_tree = base_tree
456
self.other_tree = other_tree
457
self._raw_conflicts = []
458
self.cooked_conflicts = []
459
self.reprocess = reprocess
460
self.show_base = show_base
463
self.change_reporter = change_reporter
465
self.pp = ProgressPhase("Merge phase", 3, self.pb)
470
self.this_tree.lock_tree_write()
471
self.base_tree.lock_read()
472
self.other_tree.lock_read()
473
self.tt = TreeTransform(self.this_tree, self.pb)
476
self.compute_transform()
478
results = self.tt.apply(no_conflicts=True)
479
self.write_modified(results)
481
self.this_tree.add_conflicts(self.cooked_conflicts)
482
except UnsupportedOperation:
486
self.other_tree.unlock()
487
self.base_tree.unlock()
488
self.this_tree.unlock()
491
def make_preview_transform(self):
492
self.base_tree.lock_read()
493
self.other_tree.lock_read()
494
self.tt = TransformPreview(self.this_tree)
497
self.compute_transform()
500
self.other_tree.unlock()
501
self.base_tree.unlock()
505
def compute_transform(self):
506
entries = self._entries3()
507
child_pb = ui.ui_factory.nested_progress_bar()
509
for num, (file_id, changed, parents3, names3,
510
executable3) in enumerate(entries):
511
child_pb.update('Preparing file merge', num, len(entries))
512
self._merge_names(file_id, parents3, names3)
514
file_status = self.merge_contents(file_id)
516
file_status = 'unmodified'
517
self._merge_executable(file_id,
518
executable3, file_status)
523
child_pb = ui.ui_factory.nested_progress_bar()
525
fs_conflicts = resolve_conflicts(self.tt, child_pb,
526
lambda t, c: conflict_pass(t, c, self.other_tree))
529
if self.change_reporter is not None:
530
from bzrlib import delta
531
delta.report_changes(
532
self.tt._iter_changes(), self.change_reporter)
533
self.cook_conflicts(fs_conflicts)
534
for conflict in self.cooked_conflicts:
538
"""Gather data about files modified between three trees.
540
Return a list of tuples of file_id, changed, parents3, names3,
541
executable3. changed is a boolean indicating whether the file contents
542
or kind were changed. parents3 is a tuple of parent ids for base,
543
other and this. names3 is a tuple of names for base, other and this.
544
executable3 is a tuple of execute-bit values for base, other and this.
547
iterator = self.other_tree._iter_changes(self.base_tree,
548
include_unchanged=True, specific_files=self.interesting_files,
549
extra_trees=[self.this_tree])
550
for (file_id, paths, changed, versioned, parents, names, kind,
551
executable) in iterator:
552
if (self.interesting_ids is not None and
553
file_id not in self.interesting_ids):
555
if file_id in self.this_tree.inventory:
556
entry = self.this_tree.inventory[file_id]
557
this_name = entry.name
558
this_parent = entry.parent_id
559
this_executable = entry.executable
563
this_executable = None
564
parents3 = parents + (this_parent,)
565
names3 = names + (this_name,)
566
executable3 = executable + (this_executable,)
567
result.append((file_id, changed, parents3, names3, executable3))
572
self.tt.final_kind(self.tt.root)
574
self.tt.cancel_deletion(self.tt.root)
575
if self.tt.final_file_id(self.tt.root) is None:
576
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
578
if self.other_tree.inventory.root is None:
580
other_root_file_id = self.other_tree.get_root_id()
581
other_root = self.tt.trans_id_file_id(other_root_file_id)
582
if other_root == self.tt.root:
585
self.tt.final_kind(other_root)
588
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
589
self.tt.cancel_creation(other_root)
590
self.tt.cancel_versioning(other_root)
592
def reparent_children(self, ie, target):
593
for thing, child in ie.children.iteritems():
594
trans_id = self.tt.trans_id_file_id(child.file_id)
595
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
597
def write_modified(self, results):
599
for path in results.modified_paths:
600
file_id = self.this_tree.path2id(self.this_tree.relpath(path))
603
hash = self.this_tree.get_file_sha1(file_id)
606
modified_hashes[file_id] = hash
607
self.this_tree.set_merge_modified(modified_hashes)
610
def parent(entry, file_id):
611
"""Determine the parent for a file_id (used as a key method)"""
614
return entry.parent_id
617
def name(entry, file_id):
618
"""Determine the name for a file_id (used as a key method)"""
624
def contents_sha1(tree, file_id):
625
"""Determine the sha1 of the file contents (used as a key method)."""
626
if file_id not in tree:
628
return tree.get_file_sha1(file_id)
631
def executable(tree, file_id):
632
"""Determine the executability of a file-id (used as a key method)."""
633
if file_id not in tree:
635
if tree.kind(file_id) != "file":
637
return tree.is_executable(file_id)
640
def kind(tree, file_id):
641
"""Determine the kind of a file-id (used as a key method)."""
642
if file_id not in tree:
644
return tree.kind(file_id)
647
def _three_way(base, other, this):
648
#if base == other, either they all agree, or only THIS has changed.
651
elif this not in (base, other):
653
# "Ambiguous clean merge" -- both sides have made the same change.
656
# this == base: only other has changed.
661
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
662
"""Do a three-way test on a scalar.
663
Return "this", "other" or "conflict", depending whether a value wins.
665
key_base = key(base_tree, file_id)
666
key_other = key(other_tree, file_id)
667
#if base == other, either they all agree, or only THIS has changed.
668
if key_base == key_other:
670
key_this = key(this_tree, file_id)
671
if key_this not in (key_base, key_other):
673
# "Ambiguous clean merge"
674
elif key_this == key_other:
677
assert key_this == key_base
680
def merge_names(self, file_id):
682
if file_id in tree.inventory:
683
return tree.inventory[file_id]
686
this_entry = get_entry(self.this_tree)
687
other_entry = get_entry(self.other_tree)
688
base_entry = get_entry(self.base_tree)
689
entries = (base_entry, other_entry, this_entry)
692
for entry in entries:
697
names.append(entry.name)
698
parents.append(entry.parent_id)
699
return self._merge_names(file_id, parents, names)
701
def _merge_names(self, file_id, parents, names):
702
"""Perform a merge on file_id names and parents"""
703
base_name, other_name, this_name = names
704
base_parent, other_parent, this_parent = parents
706
name_winner = self._three_way(*names)
708
parent_id_winner = self._three_way(*parents)
709
if this_name is None:
710
if name_winner == "this":
711
name_winner = "other"
712
if parent_id_winner == "this":
713
parent_id_winner = "other"
714
if name_winner == "this" and parent_id_winner == "this":
716
if name_winner == "conflict":
717
trans_id = self.tt.trans_id_file_id(file_id)
718
self._raw_conflicts.append(('name conflict', trans_id,
719
this_name, other_name))
720
if parent_id_winner == "conflict":
721
trans_id = self.tt.trans_id_file_id(file_id)
722
self._raw_conflicts.append(('parent conflict', trans_id,
723
this_parent, other_parent))
724
if other_name is None:
725
# it doesn't matter whether the result was 'other' or
726
# 'conflict'-- if there's no 'other', we leave it alone.
728
# if we get here, name_winner and parent_winner are set to safe values.
729
trans_id = self.tt.trans_id_file_id(file_id)
730
parent_id = parents[self.winner_idx[parent_id_winner]]
731
if parent_id is not None:
732
parent_trans_id = self.tt.trans_id_file_id(parent_id)
733
self.tt.adjust_path(names[self.winner_idx[name_winner]],
734
parent_trans_id, trans_id)
736
def merge_contents(self, file_id):
737
"""Performa a merge on file_id contents."""
738
def contents_pair(tree):
739
if file_id not in tree:
741
kind = tree.kind(file_id)
743
contents = tree.get_file_sha1(file_id)
744
elif kind == "symlink":
745
contents = tree.get_symlink_target(file_id)
748
return kind, contents
750
def contents_conflict():
751
trans_id = self.tt.trans_id_file_id(file_id)
752
name = self.tt.final_name(trans_id)
753
parent_id = self.tt.final_parent(trans_id)
754
if file_id in self.this_tree.inventory:
755
self.tt.unversion_file(trans_id)
756
if file_id in self.this_tree:
757
self.tt.delete_contents(trans_id)
758
file_group = self._dump_conflicts(name, parent_id, file_id,
760
self._raw_conflicts.append(('contents conflict', file_group))
762
# See SPOT run. run, SPOT, run.
763
# So we're not QUITE repeating ourselves; we do tricky things with
765
base_pair = contents_pair(self.base_tree)
766
other_pair = contents_pair(self.other_tree)
767
if base_pair == other_pair:
768
# OTHER introduced no changes
770
this_pair = contents_pair(self.this_tree)
771
if this_pair == other_pair:
772
# THIS and OTHER introduced the same changes
775
trans_id = self.tt.trans_id_file_id(file_id)
776
if this_pair == base_pair:
777
# only OTHER introduced changes
778
if file_id in self.this_tree:
779
# Remove any existing contents
780
self.tt.delete_contents(trans_id)
781
if file_id in self.other_tree:
782
# OTHER changed the file
783
create_by_entry(self.tt,
784
self.other_tree.inventory[file_id],
785
self.other_tree, trans_id)
786
if file_id not in self.this_tree.inventory:
787
self.tt.version_file(file_id, trans_id)
789
elif file_id in self.this_tree.inventory:
790
# OTHER deleted the file
791
self.tt.unversion_file(trans_id)
793
#BOTH THIS and OTHER introduced changes; scalar conflict
794
elif this_pair[0] == "file" and other_pair[0] == "file":
795
# THIS and OTHER are both files, so text merge. Either
796
# BASE is a file, or both converted to files, so at least we
797
# have agreement that output should be a file.
799
self.text_merge(file_id, trans_id)
801
return contents_conflict()
802
if file_id not in self.this_tree.inventory:
803
self.tt.version_file(file_id, trans_id)
805
self.tt.tree_kind(trans_id)
806
self.tt.delete_contents(trans_id)
811
# Scalar conflict, can't text merge. Dump conflicts
812
return contents_conflict()
814
def get_lines(self, tree, file_id):
815
"""Return the lines in a file, or an empty list."""
817
return tree.get_file(file_id).readlines()
821
def text_merge(self, file_id, trans_id):
822
"""Perform a three-way text merge on a file_id"""
823
# it's possible that we got here with base as a different type.
824
# if so, we just want two-way text conflicts.
825
if file_id in self.base_tree and \
826
self.base_tree.kind(file_id) == "file":
827
base_lines = self.get_lines(self.base_tree, file_id)
830
other_lines = self.get_lines(self.other_tree, file_id)
831
this_lines = self.get_lines(self.this_tree, file_id)
832
m3 = Merge3(base_lines, this_lines, other_lines)
833
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
834
if self.show_base is True:
835
base_marker = '|' * 7
839
def iter_merge3(retval):
840
retval["text_conflicts"] = False
841
for line in m3.merge_lines(name_a = "TREE",
842
name_b = "MERGE-SOURCE",
843
name_base = "BASE-REVISION",
844
start_marker=start_marker,
845
base_marker=base_marker,
846
reprocess=self.reprocess):
847
if line.startswith(start_marker):
848
retval["text_conflicts"] = True
849
yield line.replace(start_marker, '<' * 7)
853
merge3_iterator = iter_merge3(retval)
854
self.tt.create_file(merge3_iterator, trans_id)
855
if retval["text_conflicts"] is True:
856
self._raw_conflicts.append(('text conflict', trans_id))
857
name = self.tt.final_name(trans_id)
858
parent_id = self.tt.final_parent(trans_id)
859
file_group = self._dump_conflicts(name, parent_id, file_id,
860
this_lines, base_lines,
862
file_group.append(trans_id)
864
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
865
base_lines=None, other_lines=None, set_version=False,
867
"""Emit conflict files.
868
If this_lines, base_lines, or other_lines are omitted, they will be
869
determined automatically. If set_version is true, the .OTHER, .THIS
870
or .BASE (in that order) will be created as versioned files.
872
data = [('OTHER', self.other_tree, other_lines),
873
('THIS', self.this_tree, this_lines)]
875
data.append(('BASE', self.base_tree, base_lines))
878
for suffix, tree, lines in data:
880
trans_id = self._conflict_file(name, parent_id, tree, file_id,
882
file_group.append(trans_id)
883
if set_version and not versioned:
884
self.tt.version_file(file_id, trans_id)
888
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
890
"""Emit a single conflict file."""
891
name = name + '.' + suffix
892
trans_id = self.tt.create_path(name, parent_id)
893
entry = tree.inventory[file_id]
894
create_by_entry(self.tt, entry, tree, trans_id, lines)
897
def merge_executable(self, file_id, file_status):
898
"""Perform a merge on the execute bit."""
899
executable = [self.executable(t, file_id) for t in (self.base_tree,
900
self.other_tree, self.this_tree)]
901
self._merge_executable(file_id, executable, file_status)
903
def _merge_executable(self, file_id, executable, file_status):
904
"""Perform a merge on the execute bit."""
905
base_executable, other_executable, this_executable = executable
906
if file_status == "deleted":
908
trans_id = self.tt.trans_id_file_id(file_id)
910
if self.tt.final_kind(trans_id) != "file":
914
winner = self._three_way(*executable)
915
if winner == "conflict":
916
# There must be a None in here, if we have a conflict, but we
917
# need executability since file status was not deleted.
918
if self.executable(self.other_tree, file_id) is None:
923
if file_status == "modified":
924
executability = this_executable
925
if executability is not None:
926
trans_id = self.tt.trans_id_file_id(file_id)
927
self.tt.set_executability(executability, trans_id)
929
assert winner == "other"
930
if file_id in self.other_tree:
931
executability = other_executable
932
elif file_id in self.this_tree:
933
executability = this_executable
934
elif file_id in self.base_tree:
935
executability = base_executable
936
if executability is not None:
937
trans_id = self.tt.trans_id_file_id(file_id)
938
self.tt.set_executability(executability, trans_id)
940
def cook_conflicts(self, fs_conflicts):
941
"""Convert all conflicts into a form that doesn't depend on trans_id"""
942
from conflicts import Conflict
944
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
945
fp = FinalPaths(self.tt)
946
for conflict in self._raw_conflicts:
947
conflict_type = conflict[0]
948
if conflict_type in ('name conflict', 'parent conflict'):
949
trans_id = conflict[1]
950
conflict_args = conflict[2:]
951
if trans_id not in name_conflicts:
952
name_conflicts[trans_id] = {}
953
unique_add(name_conflicts[trans_id], conflict_type,
955
if conflict_type == 'contents conflict':
956
for trans_id in conflict[1]:
957
file_id = self.tt.final_file_id(trans_id)
958
if file_id is not None:
960
path = fp.get_path(trans_id)
961
for suffix in ('.BASE', '.THIS', '.OTHER'):
962
if path.endswith(suffix):
963
path = path[:-len(suffix)]
965
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
966
self.cooked_conflicts.append(c)
967
if conflict_type == 'text conflict':
968
trans_id = conflict[1]
969
path = fp.get_path(trans_id)
970
file_id = self.tt.final_file_id(trans_id)
971
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
972
self.cooked_conflicts.append(c)
974
for trans_id, conflicts in name_conflicts.iteritems():
976
this_parent, other_parent = conflicts['parent conflict']
977
assert this_parent != other_parent
979
this_parent = other_parent = \
980
self.tt.final_file_id(self.tt.final_parent(trans_id))
982
this_name, other_name = conflicts['name conflict']
983
assert this_name != other_name
985
this_name = other_name = self.tt.final_name(trans_id)
986
other_path = fp.get_path(trans_id)
987
if this_parent is not None and this_name is not None:
989
fp.get_path(self.tt.trans_id_file_id(this_parent))
990
this_path = pathjoin(this_parent_path, this_name)
992
this_path = "<deleted>"
993
file_id = self.tt.final_file_id(trans_id)
994
c = Conflict.factory('path conflict', path=this_path,
995
conflict_path=other_path, file_id=file_id)
996
self.cooked_conflicts.append(c)
997
self.cooked_conflicts.sort(key=Conflict.sort_key)
1000
class WeaveMerger(Merge3Merger):
1001
"""Three-way tree merger, text weave merger."""
1002
supports_reprocess = True
1003
supports_show_base = False
1005
def _merged_lines(self, file_id):
1006
"""Generate the merged lines.
1007
There is no distinction between lines that are meant to contain <<<<<<<
1010
plan = self.this_tree.plan_file_merge(file_id, self.other_tree)
1011
textmerge = PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1012
'>>>>>>> MERGE-SOURCE\n')
1013
return textmerge.merge_lines(self.reprocess)
1015
def text_merge(self, file_id, trans_id):
1016
"""Perform a (weave) text merge for a given file and file-id.
1017
If conflicts are encountered, .THIS and .OTHER files will be emitted,
1018
and a conflict will be noted.
1020
lines, conflicts = self._merged_lines(file_id)
1022
# Note we're checking whether the OUTPUT is binary in this case,
1023
# because we don't want to get into weave merge guts.
1024
check_text_lines(lines)
1025
self.tt.create_file(lines, trans_id)
1027
self._raw_conflicts.append(('text conflict', trans_id))
1028
name = self.tt.final_name(trans_id)
1029
parent_id = self.tt.final_parent(trans_id)
1030
file_group = self._dump_conflicts(name, parent_id, file_id,
1032
file_group.append(trans_id)
1035
class Diff3Merger(Merge3Merger):
1036
"""Three-way merger using external diff3 for text merging"""
1038
def dump_file(self, temp_dir, name, tree, file_id):
1039
out_path = pathjoin(temp_dir, name)
1040
out_file = open(out_path, "wb")
1042
in_file = tree.get_file(file_id)
1043
for line in in_file:
1044
out_file.write(line)
1049
def text_merge(self, file_id, trans_id):
1050
"""Perform a diff3 merge using a specified file-id and trans-id.
1051
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
1052
will be dumped, and a will be conflict noted.
1055
temp_dir = osutils.mkdtemp(prefix="bzr-")
1057
new_file = pathjoin(temp_dir, "new")
1058
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1059
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1060
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1061
status = bzrlib.patch.diff3(new_file, this, base, other)
1062
if status not in (0, 1):
1063
raise BzrError("Unhandled diff3 exit code")
1064
f = open(new_file, 'rb')
1066
self.tt.create_file(f, trans_id)
1070
name = self.tt.final_name(trans_id)
1071
parent_id = self.tt.final_parent(trans_id)
1072
self._dump_conflicts(name, parent_id, file_id)
1073
self._raw_conflicts.append(('text conflict', trans_id))
1075
osutils.rmtree(temp_dir)
1078
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1080
merge_type=Merge3Merger,
1081
interesting_ids=None,
1085
interesting_files=None,
1088
change_reporter=None):
1089
"""Primary interface for merging.
1091
typical use is probably
1092
'merge_inner(branch, branch.get_revision_tree(other_revision),
1093
branch.get_revision_tree(base_revision))'
1095
if this_tree is None:
1096
raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1097
"parameter as of bzrlib version 0.8.")
1098
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1099
pb=pb, change_reporter=change_reporter)
1100
merger.backup_files = backup_files
1101
merger.merge_type = merge_type
1102
merger.interesting_ids = interesting_ids
1103
merger.ignore_zero = ignore_zero
1104
if interesting_files:
1105
assert not interesting_ids, ('Only supply interesting_ids'
1106
' or interesting_files')
1107
merger.interesting_files = interesting_files
1108
merger.show_base = show_base
1109
merger.reprocess = reprocess
1110
merger.other_rev_id = other_rev_id
1111
merger.other_basis = other_rev_id
1112
return merger.do_merge()
1114
def get_merge_type_registry():
1115
"""Merge type registry is in bzrlib.option to avoid circular imports.
1117
This method provides a sanctioned way to retrieve it.
1119
from bzrlib import option
1120
return option._merge_type_registry
1123
def _plan_annotate_merge(annotated_a, annotated_b, ancestors_a, ancestors_b):
1124
def status_a(revision, text):
1125
if revision in ancestors_b:
1126
return 'killed-b', text
1128
return 'new-a', text
1130
def status_b(revision, text):
1131
if revision in ancestors_a:
1132
return 'killed-a', text
1134
return 'new-b', text
1136
plain_a = [t for (a, t) in annotated_a]
1137
plain_b = [t for (a, t) in annotated_b]
1138
matcher = patiencediff.PatienceSequenceMatcher(None, plain_a, plain_b)
1139
blocks = matcher.get_matching_blocks()
1142
for ai, bi, l in blocks:
1143
# process all mismatched sections
1144
# (last mismatched section is handled because blocks always
1145
# includes a 0-length last block)
1146
for revision, text in annotated_a[a_cur:ai]:
1147
yield status_a(revision, text)
1148
for revision, text in annotated_b[b_cur:bi]:
1149
yield status_b(revision, text)
1151
# and now the matched section
1154
for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
1155
assert text_a == text_b
1156
yield "unchanged", text_a