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
26
from bzrlib.branch import Branch
27
from bzrlib.conflicts import ConflictList, Conflict
28
from bzrlib.errors import (BzrCommandError,
38
WorkingTreeNotRevision,
41
from bzrlib.merge3 import Merge3
42
from bzrlib.osutils import rename, pathjoin
43
from progress import DummyProgress, ProgressPhase
44
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
45
from bzrlib.textfile import check_text_lines
46
from bzrlib.trace import mutter, warning, note
47
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
48
FinalPaths, create_by_entry, unique_add,
50
from bzrlib.versionedfile import WeaveMerge
53
# TODO: Report back as changes are merged in
55
def _get_tree(treespec, local_branch=None):
56
from bzrlib import workingtree
57
location, revno = treespec
59
tree = workingtree.WorkingTree.open_containing(location)[0]
60
return tree.branch, tree
61
branch = Branch.open_containing(location)[0]
63
revision = branch.last_revision()
65
revision = branch.get_rev_id(revno)
67
revision = NULL_REVISION
68
return branch, _get_revid_tree(branch, revision, local_branch)
71
def _get_revid_tree(branch, revision, local_branch):
73
base_tree = branch.bzrdir.open_workingtree()
75
if local_branch is not None:
76
if local_branch.base != branch.base:
77
local_branch.fetch(branch, revision)
78
base_tree = local_branch.repository.revision_tree(revision)
80
base_tree = branch.repository.revision_tree(revision)
84
def transform_tree(from_tree, to_tree, interesting_ids=None):
85
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
86
interesting_ids=interesting_ids, this_tree=from_tree)
90
def __init__(self, this_branch, other_tree=None, base_tree=None,
91
this_tree=None, pb=DummyProgress(), change_reporter=None):
93
assert this_tree is not None, "this_tree is required"
94
self.this_branch = this_branch
95
self.this_basis = this_branch.last_revision()
96
self.this_rev_id = None
97
self.this_tree = this_tree
98
self.this_revision_tree = None
99
self.this_basis_tree = None
100
self.other_tree = other_tree
101
self.base_tree = base_tree
102
self.ignore_zero = False
103
self.backup_files = False
104
self.interesting_ids = None
105
self.show_base = False
106
self.reprocess = False
109
self.change_reporter = change_reporter
111
def revision_tree(self, revision_id):
112
return self.this_branch.repository.revision_tree(revision_id)
114
def ensure_revision_trees(self):
115
if self.this_revision_tree is None:
116
self.this_basis_tree = self.this_branch.repository.revision_tree(
118
if self.this_basis == self.this_rev_id:
119
self.this_revision_tree = self.this_basis_tree
121
if self.other_rev_id is None:
122
other_basis_tree = self.revision_tree(self.other_basis)
123
changes = other_basis_tree.changes_from(self.other_tree)
124
if changes.has_changed():
125
raise WorkingTreeNotRevision(self.this_tree)
126
other_rev_id = self.other_basis
127
self.other_tree = other_basis_tree
129
def file_revisions(self, file_id):
130
self.ensure_revision_trees()
131
def get_id(tree, file_id):
132
revision_id = tree.inventory[file_id].revision
133
assert revision_id is not None
135
if self.this_rev_id is None:
136
if self.this_basis_tree.get_file_sha1(file_id) != \
137
self.this_tree.get_file_sha1(file_id):
138
raise WorkingTreeNotRevision(self.this_tree)
140
trees = (self.this_basis_tree, self.other_tree)
141
return [get_id(tree, file_id) for tree in trees]
143
def check_basis(self, check_clean, require_commits=True):
144
if self.this_basis is None and require_commits is True:
145
raise BzrCommandError("This branch has no commits."
146
" (perhaps you would prefer 'bzr pull')")
149
if self.this_basis != self.this_rev_id:
150
raise BzrCommandError("Working tree has uncommitted changes.")
152
def compare_basis(self):
153
changes = self.this_tree.changes_from(self.this_tree.basis_tree())
154
if not changes.has_changed():
155
self.this_rev_id = self.this_basis
157
def set_interesting_files(self, file_list):
159
self._set_interesting_files(file_list)
160
except NotVersionedError, e:
161
raise BzrCommandError("%s is not a source file in any"
164
def _set_interesting_files(self, file_list):
165
"""Set the list of interesting ids from a list of files."""
166
if file_list is None:
167
self.interesting_ids = None
170
interesting_ids = set()
171
for path in file_list:
173
for tree in (self.this_tree, self.base_tree, self.other_tree):
174
file_id = tree.inventory.path2id(path)
175
if file_id is not None:
176
interesting_ids.add(file_id)
179
raise NotVersionedError(path=path)
180
self.interesting_ids = interesting_ids
182
def set_pending(self):
183
if not self.base_is_ancestor:
185
if self.other_rev_id is None:
187
ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
188
if self.other_rev_id in ancestry:
190
self.this_tree.add_parent_tree((self.other_rev_id, self.other_tree))
192
def set_other(self, other_revision):
193
"""Set the revision and tree to merge from.
195
This sets the other_tree, other_rev_id, other_basis attributes.
197
:param other_revision: The [path, revision] list to merge from.
199
other_branch, self.other_tree = _get_tree(other_revision,
201
if other_revision[1] == -1:
202
self.other_rev_id = other_branch.last_revision()
203
if self.other_rev_id is None:
204
raise NoCommits(other_branch)
205
self.other_basis = self.other_rev_id
206
elif other_revision[1] is not None:
207
self.other_rev_id = other_branch.get_rev_id(other_revision[1])
208
self.other_basis = self.other_rev_id
210
self.other_rev_id = None
211
self.other_basis = other_branch.last_revision()
212
if self.other_basis is None:
213
raise NoCommits(other_branch)
214
if other_branch.base != self.this_branch.base:
215
self.this_branch.fetch(other_branch, last_revision=self.other_basis)
218
self.set_base([None, None])
220
def set_base(self, base_revision):
221
"""Set the base revision to use for the merge.
223
:param base_revision: A 2-list containing a path and revision number.
225
mutter("doing merge() with no base_revision specified")
226
if base_revision == [None, None]:
228
pb = ui.ui_factory.nested_progress_bar()
230
this_repo = self.this_branch.repository
231
self.base_rev_id = common_ancestor(self.this_basis,
236
except NoCommonAncestor:
237
raise UnrelatedBranches()
238
self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
240
self.base_is_ancestor = True
242
base_branch, self.base_tree = _get_tree(base_revision)
243
if base_revision[1] == -1:
244
self.base_rev_id = base_branch.last_revision()
245
elif base_revision[1] is None:
246
self.base_rev_id = None
248
self.base_rev_id = base_branch.get_rev_id(base_revision[1])
249
if self.this_branch.base != base_branch.base:
250
self.this_branch.fetch(base_branch)
251
self.base_is_ancestor = is_ancestor(self.this_basis,
256
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
257
'other_tree': self.other_tree,
258
'interesting_ids': self.interesting_ids,
260
if self.merge_type.requires_base:
261
kwargs['base_tree'] = self.base_tree
262
if self.merge_type.supports_reprocess:
263
kwargs['reprocess'] = self.reprocess
265
raise BzrError("Conflict reduction is not supported for merge"
266
" type %s." % self.merge_type)
267
if self.merge_type.supports_show_base:
268
kwargs['show_base'] = self.show_base
270
raise BzrError("Showing base is not supported for this"
271
" merge type. %s" % self.merge_type)
272
merge = self.merge_type(pb=self._pb,
273
change_reporter=self.change_reporter,
275
if len(merge.cooked_conflicts) == 0:
276
if not self.ignore_zero:
277
note("All changes applied successfully.")
279
note("%d conflicts encountered." % len(merge.cooked_conflicts))
281
return len(merge.cooked_conflicts)
283
def regen_inventory(self, new_entries):
284
old_entries = self.this_tree.read_working_inventory()
288
for path, file_id in new_entries:
291
new_entries_map[file_id] = path
293
def id2path(file_id):
294
path = new_entries_map.get(file_id)
297
entry = old_entries[file_id]
298
if entry.parent_id is None:
300
return pathjoin(id2path(entry.parent_id), entry.name)
302
for file_id in old_entries:
303
entry = old_entries[file_id]
304
path = id2path(file_id)
305
if file_id in self.base_tree.inventory:
306
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
308
executable = getattr(entry, 'executable', False)
309
new_inventory[file_id] = (path, file_id, entry.parent_id,
310
entry.kind, executable)
312
by_path[path] = file_id
317
for path, file_id in new_entries:
319
del new_inventory[file_id]
322
new_path_list.append((path, file_id))
323
if file_id not in old_entries:
325
# Ensure no file is added before its parent
327
for path, file_id in new_path_list:
331
parent = by_path[os.path.dirname(path)]
332
abspath = pathjoin(self.this_tree.basedir, path)
333
kind = osutils.file_kind(abspath)
334
if file_id in self.base_tree.inventory:
335
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
338
new_inventory[file_id] = (path, file_id, parent, kind, executable)
339
by_path[path] = file_id
341
# Get a list in insertion order
342
new_inventory_list = new_inventory.values()
343
mutter ("""Inventory regeneration:
344
old length: %i insertions: %i deletions: %i new_length: %i"""\
345
% (len(old_entries), insertions, deletions,
346
len(new_inventory_list)))
347
assert len(new_inventory_list) == len(old_entries) + insertions\
349
new_inventory_list.sort()
350
return new_inventory_list
353
class Merge3Merger(object):
354
"""Three-way merger that uses the merge3 text merger"""
356
supports_reprocess = True
357
supports_show_base = True
358
history_based = False
360
def __init__(self, working_tree, this_tree, base_tree, other_tree,
361
interesting_ids=None, reprocess=False, show_base=False,
362
pb=DummyProgress(), pp=None, change_reporter=None):
363
"""Initialize the merger object and perform the merge."""
364
object.__init__(self)
365
self.this_tree = working_tree
366
self.base_tree = base_tree
367
self.other_tree = other_tree
368
self._raw_conflicts = []
369
self.cooked_conflicts = []
370
self.reprocess = reprocess
371
self.show_base = show_base
374
self.change_reporter = change_reporter
376
self.pp = ProgressPhase("Merge phase", 3, self.pb)
378
if interesting_ids is not None:
379
all_ids = interesting_ids
381
all_ids = set(base_tree)
382
all_ids.update(other_tree)
383
working_tree.lock_tree_write()
384
self.tt = TreeTransform(working_tree, self.pb)
387
child_pb = ui.ui_factory.nested_progress_bar()
389
for num, file_id in enumerate(all_ids):
390
child_pb.update('Preparing file merge', num, len(all_ids))
391
self.merge_names(file_id)
392
file_status = self.merge_contents(file_id)
393
self.merge_executable(file_id, file_status)
398
child_pb = ui.ui_factory.nested_progress_bar()
400
fs_conflicts = resolve_conflicts(self.tt, child_pb)
403
if change_reporter is not None:
404
from bzrlib import delta
405
delta.report_changes(self.tt._iter_changes(), change_reporter)
406
self.cook_conflicts(fs_conflicts)
407
for conflict in self.cooked_conflicts:
410
results = self.tt.apply()
411
self.write_modified(results)
413
working_tree.add_conflicts(self.cooked_conflicts)
414
except UnsupportedOperation:
418
working_tree.unlock()
423
self.tt.final_kind(self.tt.root)
425
self.tt.cancel_deletion(self.tt.root)
426
if self.tt.final_file_id(self.tt.root) is None:
427
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
429
if self.other_tree.inventory.root is None:
431
other_root_file_id = self.other_tree.inventory.root.file_id
432
other_root = self.tt.trans_id_file_id(other_root_file_id)
433
if other_root == self.tt.root:
436
self.tt.final_kind(other_root)
439
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
440
self.tt.cancel_creation(other_root)
441
self.tt.cancel_versioning(other_root)
443
def reparent_children(self, ie, target):
444
for thing, child in ie.children.iteritems():
445
trans_id = self.tt.trans_id_file_id(child.file_id)
446
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
448
def write_modified(self, results):
450
for path in results.modified_paths:
451
file_id = self.this_tree.path2id(self.this_tree.relpath(path))
454
hash = self.this_tree.get_file_sha1(file_id)
457
modified_hashes[file_id] = hash
458
self.this_tree.set_merge_modified(modified_hashes)
461
def parent(entry, file_id):
462
"""Determine the parent for a file_id (used as a key method)"""
465
return entry.parent_id
468
def name(entry, file_id):
469
"""Determine the name for a file_id (used as a key method)"""
475
def contents_sha1(tree, file_id):
476
"""Determine the sha1 of the file contents (used as a key method)."""
477
if file_id not in tree:
479
return tree.get_file_sha1(file_id)
482
def executable(tree, file_id):
483
"""Determine the executability of a file-id (used as a key method)."""
484
if file_id not in tree:
486
if tree.kind(file_id) != "file":
488
return tree.is_executable(file_id)
491
def kind(tree, file_id):
492
"""Determine the kind of a file-id (used as a key method)."""
493
if file_id not in tree:
495
return tree.kind(file_id)
498
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
499
"""Do a three-way test on a scalar.
500
Return "this", "other" or "conflict", depending whether a value wins.
502
key_base = key(base_tree, file_id)
503
key_other = key(other_tree, file_id)
504
#if base == other, either they all agree, or only THIS has changed.
505
if key_base == key_other:
507
key_this = key(this_tree, file_id)
508
if key_this not in (key_base, key_other):
510
# "Ambiguous clean merge"
511
elif key_this == key_other:
514
assert key_this == key_base
517
def merge_names(self, file_id):
518
"""Perform a merge on file_id names and parents"""
520
if file_id in tree.inventory:
521
return tree.inventory[file_id]
524
this_entry = get_entry(self.this_tree)
525
other_entry = get_entry(self.other_tree)
526
base_entry = get_entry(self.base_tree)
527
name_winner = self.scalar_three_way(this_entry, base_entry,
528
other_entry, file_id, self.name)
529
parent_id_winner = self.scalar_three_way(this_entry, base_entry,
530
other_entry, file_id,
532
if this_entry is None:
533
if name_winner == "this":
534
name_winner = "other"
535
if parent_id_winner == "this":
536
parent_id_winner = "other"
537
if name_winner == "this" and parent_id_winner == "this":
539
if name_winner == "conflict":
540
trans_id = self.tt.trans_id_file_id(file_id)
541
self._raw_conflicts.append(('name conflict', trans_id,
542
self.name(this_entry, file_id),
543
self.name(other_entry, file_id)))
544
if parent_id_winner == "conflict":
545
trans_id = self.tt.trans_id_file_id(file_id)
546
self._raw_conflicts.append(('parent conflict', trans_id,
547
self.parent(this_entry, file_id),
548
self.parent(other_entry, file_id)))
549
if other_entry is None:
550
# it doesn't matter whether the result was 'other' or
551
# 'conflict'-- if there's no 'other', we leave it alone.
553
# if we get here, name_winner and parent_winner are set to safe values.
554
winner_entry = {"this": this_entry, "other": other_entry,
555
"conflict": other_entry}
556
trans_id = self.tt.trans_id_file_id(file_id)
557
parent_id = winner_entry[parent_id_winner].parent_id
558
if parent_id is not None:
559
parent_trans_id = self.tt.trans_id_file_id(parent_id)
560
self.tt.adjust_path(winner_entry[name_winner].name,
561
parent_trans_id, trans_id)
563
def merge_contents(self, file_id):
564
"""Performa a merge on file_id contents."""
565
def contents_pair(tree):
566
if file_id not in tree:
568
kind = tree.kind(file_id)
570
contents = tree.get_file_sha1(file_id)
571
elif kind == "symlink":
572
contents = tree.get_symlink_target(file_id)
575
return kind, contents
577
def contents_conflict():
578
trans_id = self.tt.trans_id_file_id(file_id)
579
name = self.tt.final_name(trans_id)
580
parent_id = self.tt.final_parent(trans_id)
581
if file_id in self.this_tree.inventory:
582
self.tt.unversion_file(trans_id)
583
if file_id in self.this_tree:
584
self.tt.delete_contents(trans_id)
585
file_group = self._dump_conflicts(name, parent_id, file_id,
587
self._raw_conflicts.append(('contents conflict', file_group))
589
# See SPOT run. run, SPOT, run.
590
# So we're not QUITE repeating ourselves; we do tricky things with
592
base_pair = contents_pair(self.base_tree)
593
other_pair = contents_pair(self.other_tree)
594
if base_pair == other_pair:
595
# OTHER introduced no changes
597
this_pair = contents_pair(self.this_tree)
598
if this_pair == other_pair:
599
# THIS and OTHER introduced the same changes
602
trans_id = self.tt.trans_id_file_id(file_id)
603
if this_pair == base_pair:
604
# only OTHER introduced changes
605
if file_id in self.this_tree:
606
# Remove any existing contents
607
self.tt.delete_contents(trans_id)
608
if file_id in self.other_tree:
609
# OTHER changed the file
610
create_by_entry(self.tt,
611
self.other_tree.inventory[file_id],
612
self.other_tree, trans_id)
613
if file_id not in self.this_tree.inventory:
614
self.tt.version_file(file_id, trans_id)
616
elif file_id in self.this_tree.inventory:
617
# OTHER deleted the file
618
self.tt.unversion_file(trans_id)
620
#BOTH THIS and OTHER introduced changes; scalar conflict
621
elif this_pair[0] == "file" and other_pair[0] == "file":
622
# THIS and OTHER are both files, so text merge. Either
623
# BASE is a file, or both converted to files, so at least we
624
# have agreement that output should be a file.
626
self.text_merge(file_id, trans_id)
628
return contents_conflict()
629
if file_id not in self.this_tree.inventory:
630
self.tt.version_file(file_id, trans_id)
632
self.tt.tree_kind(trans_id)
633
self.tt.delete_contents(trans_id)
638
# Scalar conflict, can't text merge. Dump conflicts
639
return contents_conflict()
641
def get_lines(self, tree, file_id):
642
"""Return the lines in a file, or an empty list."""
644
return tree.get_file(file_id).readlines()
648
def text_merge(self, file_id, trans_id):
649
"""Perform a three-way text merge on a file_id"""
650
# it's possible that we got here with base as a different type.
651
# if so, we just want two-way text conflicts.
652
if file_id in self.base_tree and \
653
self.base_tree.kind(file_id) == "file":
654
base_lines = self.get_lines(self.base_tree, file_id)
657
other_lines = self.get_lines(self.other_tree, file_id)
658
this_lines = self.get_lines(self.this_tree, file_id)
659
m3 = Merge3(base_lines, this_lines, other_lines)
660
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
661
if self.show_base is True:
662
base_marker = '|' * 7
666
def iter_merge3(retval):
667
retval["text_conflicts"] = False
668
for line in m3.merge_lines(name_a = "TREE",
669
name_b = "MERGE-SOURCE",
670
name_base = "BASE-REVISION",
671
start_marker=start_marker,
672
base_marker=base_marker,
673
reprocess=self.reprocess):
674
if line.startswith(start_marker):
675
retval["text_conflicts"] = True
676
yield line.replace(start_marker, '<' * 7)
680
merge3_iterator = iter_merge3(retval)
681
self.tt.create_file(merge3_iterator, trans_id)
682
if retval["text_conflicts"] is True:
683
self._raw_conflicts.append(('text conflict', trans_id))
684
name = self.tt.final_name(trans_id)
685
parent_id = self.tt.final_parent(trans_id)
686
file_group = self._dump_conflicts(name, parent_id, file_id,
687
this_lines, base_lines,
689
file_group.append(trans_id)
691
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
692
base_lines=None, other_lines=None, set_version=False,
694
"""Emit conflict files.
695
If this_lines, base_lines, or other_lines are omitted, they will be
696
determined automatically. If set_version is true, the .OTHER, .THIS
697
or .BASE (in that order) will be created as versioned files.
699
data = [('OTHER', self.other_tree, other_lines),
700
('THIS', self.this_tree, this_lines)]
702
data.append(('BASE', self.base_tree, base_lines))
705
for suffix, tree, lines in data:
707
trans_id = self._conflict_file(name, parent_id, tree, file_id,
709
file_group.append(trans_id)
710
if set_version and not versioned:
711
self.tt.version_file(file_id, trans_id)
715
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
717
"""Emit a single conflict file."""
718
name = name + '.' + suffix
719
trans_id = self.tt.create_path(name, parent_id)
720
entry = tree.inventory[file_id]
721
create_by_entry(self.tt, entry, tree, trans_id, lines)
724
def merge_executable(self, file_id, file_status):
725
"""Perform a merge on the execute bit."""
726
if file_status == "deleted":
728
trans_id = self.tt.trans_id_file_id(file_id)
730
if self.tt.final_kind(trans_id) != "file":
734
winner = self.scalar_three_way(self.this_tree, self.base_tree,
735
self.other_tree, file_id,
737
if winner == "conflict":
738
# There must be a None in here, if we have a conflict, but we
739
# need executability since file status was not deleted.
740
if self.executable(self.other_tree, file_id) is None:
745
if file_status == "modified":
746
executability = self.this_tree.is_executable(file_id)
747
if executability is not None:
748
trans_id = self.tt.trans_id_file_id(file_id)
749
self.tt.set_executability(executability, trans_id)
751
assert winner == "other"
752
if file_id in self.other_tree:
753
executability = self.other_tree.is_executable(file_id)
754
elif file_id in self.this_tree:
755
executability = self.this_tree.is_executable(file_id)
756
elif file_id in self.base_tree:
757
executability = self.base_tree.is_executable(file_id)
758
if executability is not None:
759
trans_id = self.tt.trans_id_file_id(file_id)
760
self.tt.set_executability(executability, trans_id)
762
def cook_conflicts(self, fs_conflicts):
763
"""Convert all conflicts into a form that doesn't depend on trans_id"""
764
from conflicts import Conflict
766
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
767
fp = FinalPaths(self.tt)
768
for conflict in self._raw_conflicts:
769
conflict_type = conflict[0]
770
if conflict_type in ('name conflict', 'parent conflict'):
771
trans_id = conflict[1]
772
conflict_args = conflict[2:]
773
if trans_id not in name_conflicts:
774
name_conflicts[trans_id] = {}
775
unique_add(name_conflicts[trans_id], conflict_type,
777
if conflict_type == 'contents conflict':
778
for trans_id in conflict[1]:
779
file_id = self.tt.final_file_id(trans_id)
780
if file_id is not None:
782
path = fp.get_path(trans_id)
783
for suffix in ('.BASE', '.THIS', '.OTHER'):
784
if path.endswith(suffix):
785
path = path[:-len(suffix)]
787
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
788
self.cooked_conflicts.append(c)
789
if conflict_type == 'text conflict':
790
trans_id = conflict[1]
791
path = fp.get_path(trans_id)
792
file_id = self.tt.final_file_id(trans_id)
793
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
794
self.cooked_conflicts.append(c)
796
for trans_id, conflicts in name_conflicts.iteritems():
798
this_parent, other_parent = conflicts['parent conflict']
799
assert this_parent != other_parent
801
this_parent = other_parent = \
802
self.tt.final_file_id(self.tt.final_parent(trans_id))
804
this_name, other_name = conflicts['name conflict']
805
assert this_name != other_name
807
this_name = other_name = self.tt.final_name(trans_id)
808
other_path = fp.get_path(trans_id)
809
if this_parent is not None:
811
fp.get_path(self.tt.trans_id_file_id(this_parent))
812
this_path = pathjoin(this_parent_path, this_name)
814
this_path = "<deleted>"
815
file_id = self.tt.final_file_id(trans_id)
816
c = Conflict.factory('path conflict', path=this_path,
817
conflict_path=other_path, file_id=file_id)
818
self.cooked_conflicts.append(c)
819
self.cooked_conflicts.sort(key=Conflict.sort_key)
822
class WeaveMerger(Merge3Merger):
823
"""Three-way tree merger, text weave merger."""
824
supports_reprocess = True
825
supports_show_base = False
827
def __init__(self, working_tree, this_tree, base_tree, other_tree,
828
interesting_ids=None, pb=DummyProgress(), pp=None,
829
reprocess=False, change_reporter=None):
830
self.this_revision_tree = self._get_revision_tree(this_tree)
831
self.other_revision_tree = self._get_revision_tree(other_tree)
832
super(WeaveMerger, self).__init__(working_tree, this_tree,
833
base_tree, other_tree,
834
interesting_ids=interesting_ids,
835
pb=pb, pp=pp, reprocess=reprocess,
836
change_reporter=change_reporter)
838
def _get_revision_tree(self, tree):
839
"""Return a revision tree related to this tree.
840
If the tree is a WorkingTree, the basis will be returned.
842
if getattr(tree, 'get_weave', False) is False:
843
# If we have a WorkingTree, try using the basis
844
return tree.branch.basis_tree()
848
def _check_file(self, file_id):
849
"""Check that the revision tree's version of the file matches."""
850
for tree, rt in ((self.this_tree, self.this_revision_tree),
851
(self.other_tree, self.other_revision_tree)):
854
if tree.get_file_sha1(file_id) != rt.get_file_sha1(file_id):
855
raise WorkingTreeNotRevision(self.this_tree)
857
def _merged_lines(self, file_id):
858
"""Generate the merged lines.
859
There is no distinction between lines that are meant to contain <<<<<<<
862
weave = self.this_revision_tree.get_weave(file_id)
863
this_revision_id = self.this_revision_tree.inventory[file_id].revision
864
other_revision_id = \
865
self.other_revision_tree.inventory[file_id].revision
866
wm = WeaveMerge(weave, this_revision_id, other_revision_id,
867
'<<<<<<< TREE\n', '>>>>>>> MERGE-SOURCE\n')
868
return wm.merge_lines(self.reprocess)
870
def text_merge(self, file_id, trans_id):
871
"""Perform a (weave) text merge for a given file and file-id.
872
If conflicts are encountered, .THIS and .OTHER files will be emitted,
873
and a conflict will be noted.
875
self._check_file(file_id)
876
lines, conflicts = self._merged_lines(file_id)
878
# Note we're checking whether the OUTPUT is binary in this case,
879
# because we don't want to get into weave merge guts.
880
check_text_lines(lines)
881
self.tt.create_file(lines, trans_id)
883
self._raw_conflicts.append(('text conflict', trans_id))
884
name = self.tt.final_name(trans_id)
885
parent_id = self.tt.final_parent(trans_id)
886
file_group = self._dump_conflicts(name, parent_id, file_id,
888
file_group.append(trans_id)
891
class Diff3Merger(Merge3Merger):
892
"""Three-way merger using external diff3 for text merging"""
894
def dump_file(self, temp_dir, name, tree, file_id):
895
out_path = pathjoin(temp_dir, name)
896
out_file = open(out_path, "wb")
898
in_file = tree.get_file(file_id)
905
def text_merge(self, file_id, trans_id):
906
"""Perform a diff3 merge using a specified file-id and trans-id.
907
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
908
will be dumped, and a will be conflict noted.
911
temp_dir = osutils.mkdtemp(prefix="bzr-")
913
new_file = pathjoin(temp_dir, "new")
914
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
915
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
916
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
917
status = bzrlib.patch.diff3(new_file, this, base, other)
918
if status not in (0, 1):
919
raise BzrError("Unhandled diff3 exit code")
920
f = open(new_file, 'rb')
922
self.tt.create_file(f, trans_id)
926
name = self.tt.final_name(trans_id)
927
parent_id = self.tt.final_parent(trans_id)
928
self._dump_conflicts(name, parent_id, file_id)
929
self._raw_conflicts.append(('text conflict', trans_id))
931
osutils.rmtree(temp_dir)
934
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
936
merge_type=Merge3Merger,
937
interesting_ids=None,
941
interesting_files=None,
944
change_reporter=None):
945
"""Primary interface for merging.
947
typical use is probably
948
'merge_inner(branch, branch.get_revision_tree(other_revision),
949
branch.get_revision_tree(base_revision))'
951
if this_tree is None:
952
warnings.warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
953
"bzrlib version 0.8.",
956
this_tree = this_branch.bzrdir.open_workingtree()
957
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
958
pb=pb, change_reporter=change_reporter)
959
merger.backup_files = backup_files
960
merger.merge_type = merge_type
961
merger.interesting_ids = interesting_ids
962
merger.ignore_zero = ignore_zero
963
if interesting_files:
964
assert not interesting_ids, ('Only supply interesting_ids'
965
' or interesting_files')
966
merger._set_interesting_files(interesting_files)
967
merger.show_base = show_base
968
merger.reprocess = reprocess
969
merger.other_rev_id = other_rev_id
970
merger.other_basis = other_rev_id
971
return merger.do_merge()
973
def get_merge_type_registry():
974
"""Merge type registry is in bzrlib.option to avoid circular imports.
976
This method provides a sanctioned way to retrieve it.
978
from bzrlib import option
979
return option._merge_type_registry