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
20
from tempfile import mkdtemp
23
from bzrlib.branch import Branch
24
from bzrlib.conflicts import ConflictList, Conflict
25
from bzrlib.delta import compare_trees
26
from bzrlib.errors import (BzrCommandError,
36
WorkingTreeNotRevision,
39
from bzrlib.merge3 import Merge3
41
from bzrlib.osutils import rename, pathjoin, rmtree
42
from progress import DummyProgress, ProgressPhase
43
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
44
from bzrlib.symbol_versioning import *
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
location, revno = treespec
57
branch = Branch.open_containing(location)[0]
61
revision = branch.last_revision()
63
revision = branch.get_rev_id(revno)
65
revision = NULL_REVISION
66
return branch, _get_revid_tree(branch, revision, local_branch)
69
def _get_revid_tree(branch, revision, local_branch):
71
base_tree = branch.bzrdir.open_workingtree()
73
if local_branch is not None:
74
if local_branch.base != branch.base:
75
local_branch.fetch(branch, revision)
76
base_tree = local_branch.repository.revision_tree(revision)
78
base_tree = branch.repository.revision_tree(revision)
82
def transform_tree(from_tree, to_tree, interesting_ids=None):
83
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
84
interesting_ids=interesting_ids, this_tree=from_tree)
88
def __init__(self, this_branch, other_tree=None, base_tree=None,
89
this_tree=None, pb=DummyProgress()):
91
assert this_tree is not None, "this_tree is required"
92
self.this_branch = this_branch
93
self.this_basis = this_branch.last_revision()
94
self.this_rev_id = None
95
self.this_tree = this_tree
96
self.this_revision_tree = None
97
self.this_basis_tree = None
98
self.other_tree = other_tree
99
self.base_tree = base_tree
100
self.ignore_zero = False
101
self.backup_files = False
102
self.interesting_ids = None
103
self.show_base = False
104
self.reprocess = False
109
def revision_tree(self, revision_id):
110
return self.this_branch.repository.revision_tree(revision_id)
112
def ensure_revision_trees(self):
113
if self.this_revision_tree is None:
114
self.this_basis_tree = self.this_branch.repository.revision_tree(
116
if self.this_basis == self.this_rev_id:
117
self.this_revision_tree = self.this_basis_tree
119
if self.other_rev_id is None:
120
other_basis_tree = self.revision_tree(self.other_basis)
121
changes = compare_trees(self.other_tree, other_basis_tree)
122
if changes.has_changed():
123
raise WorkingTreeNotRevision(self.this_tree)
124
other_rev_id = other_basis
125
self.other_tree = other_basis_tree
127
def file_revisions(self, file_id):
128
self.ensure_revision_trees()
129
def get_id(tree, file_id):
130
revision_id = tree.inventory[file_id].revision
131
assert revision_id is not None
133
if self.this_rev_id is None:
134
if self.this_basis_tree.get_file_sha1(file_id) != \
135
self.this_tree.get_file_sha1(file_id):
136
raise WorkingTreeNotRevision(self.this_tree)
138
trees = (self.this_basis_tree, self.other_tree)
139
return [get_id(tree, file_id) for tree in trees]
141
def check_basis(self, check_clean, require_commits=True):
142
if self.this_basis is None and require_commits is True:
143
raise BzrCommandError("This branch has no commits")
146
if self.this_basis != self.this_rev_id:
147
raise BzrCommandError("Working tree has uncommitted changes.")
149
def compare_basis(self):
150
changes = compare_trees(self.this_tree,
151
self.this_tree.basis_tree(), False)
152
if not changes.has_changed():
153
self.this_rev_id = self.this_basis
155
def set_interesting_files(self, file_list):
157
self._set_interesting_files(file_list)
158
except NotVersionedError, e:
159
raise BzrCommandError("%s is not a source file in any"
162
def _set_interesting_files(self, file_list):
163
"""Set the list of interesting ids from a list of files."""
164
if file_list is None:
165
self.interesting_ids = None
168
interesting_ids = set()
169
for path in file_list:
171
for tree in (self.this_tree, self.base_tree, self.other_tree):
172
file_id = tree.inventory.path2id(path)
173
if file_id is not None:
174
interesting_ids.add(file_id)
177
raise NotVersionedError(path=path)
178
self.interesting_ids = interesting_ids
180
def set_pending(self):
181
if not self.base_is_ancestor:
183
if self.other_rev_id is None:
185
ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
186
if self.other_rev_id in ancestry:
188
self.this_tree.add_pending_merge(self.other_rev_id)
190
def set_other(self, other_revision):
191
other_branch, self.other_tree = _get_tree(other_revision,
193
if other_revision[1] == -1:
194
self.other_rev_id = other_branch.last_revision()
195
if self.other_rev_id is None:
196
raise NoCommits(other_branch)
197
self.other_basis = self.other_rev_id
198
elif other_revision[1] is not None:
199
self.other_rev_id = other_branch.get_rev_id(other_revision[1])
200
self.other_basis = self.other_rev_id
202
self.other_rev_id = None
203
self.other_basis = other_branch.last_revision()
204
if self.other_basis is None:
205
raise NoCommits(other_branch)
206
if other_branch.base != self.this_branch.base:
207
self.this_branch.fetch(other_branch, last_revision=self.other_basis)
210
self.set_base([None, None])
212
def set_base(self, base_revision):
213
mutter("doing merge() with no base_revision specified")
214
if base_revision == [None, None]:
216
pb = bzrlib.ui.ui_factory.nested_progress_bar()
218
this_repo = self.this_branch.repository
219
self.base_rev_id = common_ancestor(self.this_basis,
224
except NoCommonAncestor:
225
raise UnrelatedBranches()
226
self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
228
self.base_is_ancestor = True
230
base_branch, self.base_tree = _get_tree(base_revision)
231
if base_revision[1] == -1:
232
self.base_rev_id = base_branch.last_revision()
233
elif base_revision[1] is None:
234
self.base_rev_id = None
236
self.base_rev_id = base_branch.get_rev_id(base_revision[1])
237
if self.this_branch.base != base_branch.base:
238
self.this_branch.fetch(base_branch)
239
self.base_is_ancestor = is_ancestor(self.this_basis,
244
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
245
'other_tree': self.other_tree,
246
'interesting_ids': self.interesting_ids,
248
if self.merge_type.requires_base:
249
kwargs['base_tree'] = self.base_tree
250
if self.merge_type.supports_reprocess:
251
kwargs['reprocess'] = self.reprocess
253
raise BzrError("Conflict reduction is not supported for merge"
254
" type %s." % self.merge_type)
255
if self.merge_type.supports_show_base:
256
kwargs['show_base'] = self.show_base
258
raise BzrError("Showing base is not supported for this"
259
" merge type. %s" % self.merge_type)
260
merge = self.merge_type(pb=self._pb, **kwargs)
261
if len(merge.cooked_conflicts) == 0:
262
if not self.ignore_zero:
263
note("All changes applied successfully.")
265
note("%d conflicts encountered." % len(merge.cooked_conflicts))
267
return len(merge.cooked_conflicts)
269
def regen_inventory(self, new_entries):
270
old_entries = self.this_tree.read_working_inventory()
274
for path, file_id in new_entries:
277
new_entries_map[file_id] = path
279
def id2path(file_id):
280
path = new_entries_map.get(file_id)
283
entry = old_entries[file_id]
284
if entry.parent_id is None:
286
return pathjoin(id2path(entry.parent_id), entry.name)
288
for file_id in old_entries:
289
entry = old_entries[file_id]
290
path = id2path(file_id)
291
if file_id in self.base_tree.inventory:
292
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
294
executable = getattr(entry, 'executable', False)
295
new_inventory[file_id] = (path, file_id, entry.parent_id,
296
entry.kind, executable)
298
by_path[path] = file_id
303
for path, file_id in new_entries:
305
del new_inventory[file_id]
308
new_path_list.append((path, file_id))
309
if file_id not in old_entries:
311
# Ensure no file is added before its parent
313
for path, file_id in new_path_list:
317
parent = by_path[os.path.dirname(path)]
318
abspath = pathjoin(self.this_tree.basedir, path)
319
kind = bzrlib.osutils.file_kind(abspath)
320
if file_id in self.base_tree.inventory:
321
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
324
new_inventory[file_id] = (path, file_id, parent, kind, executable)
325
by_path[path] = file_id
327
# Get a list in insertion order
328
new_inventory_list = new_inventory.values()
329
mutter ("""Inventory regeneration:
330
old length: %i insertions: %i deletions: %i new_length: %i"""\
331
% (len(old_entries), insertions, deletions,
332
len(new_inventory_list)))
333
assert len(new_inventory_list) == len(old_entries) + insertions\
335
new_inventory_list.sort()
336
return new_inventory_list
339
class Merge3Merger(object):
340
"""Three-way merger that uses the merge3 text merger"""
342
supports_reprocess = True
343
supports_show_base = True
344
history_based = False
346
def __init__(self, working_tree, this_tree, base_tree, other_tree,
347
interesting_ids=None, reprocess=False, show_base=False,
348
pb=DummyProgress(), pp=None):
349
"""Initialize the merger object and perform the merge."""
350
object.__init__(self)
351
self.this_tree = working_tree
352
self.base_tree = base_tree
353
self.other_tree = other_tree
354
self._raw_conflicts = []
355
self.cooked_conflicts = []
356
self.reprocess = reprocess
357
self.show_base = show_base
361
self.pp = ProgressPhase("Merge phase", 3, self.pb)
363
if interesting_ids is not None:
364
all_ids = interesting_ids
366
all_ids = set(base_tree)
367
all_ids.update(other_tree)
368
working_tree.lock_write()
369
self.tt = TreeTransform(working_tree, self.pb)
372
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
374
for num, file_id in enumerate(all_ids):
375
child_pb.update('Preparing file merge', num, len(all_ids))
376
self.merge_names(file_id)
377
file_status = self.merge_contents(file_id)
378
self.merge_executable(file_id, file_status)
383
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
385
fs_conflicts = resolve_conflicts(self.tt, child_pb)
388
self.cook_conflicts(fs_conflicts)
389
for conflict in self.cooked_conflicts:
392
results = self.tt.apply()
393
self.write_modified(results)
395
working_tree.set_conflicts(ConflictList(self.cooked_conflicts))
396
except UnsupportedOperation:
403
working_tree.unlock()
408
self.tt.final_kind(self.tt.root)
410
self.tt.cancel_deletion(self.tt.root)
411
if self.tt.final_file_id(self.tt.root) is None:
412
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
414
if self.other_tree.inventory.root is None:
416
other_root_file_id = self.other_tree.inventory.root.file_id
417
other_root = self.tt.trans_id_file_id(other_root_file_id)
418
if other_root == self.tt.root:
421
self.tt.final_kind(other_root)
424
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
425
self.tt.cancel_creation(other_root)
426
self.tt.cancel_versioning(other_root)
428
def reparent_children(self, ie, target):
429
for thing, child in ie.children.iteritems():
430
trans_id = self.tt.trans_id_file_id(child.file_id)
431
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
433
def write_modified(self, results):
435
for path in results.modified_paths:
436
file_id = self.this_tree.path2id(self.this_tree.relpath(path))
439
hash = self.this_tree.get_file_sha1(file_id)
442
modified_hashes[file_id] = hash
443
self.this_tree.set_merge_modified(modified_hashes)
446
def parent(entry, file_id):
447
"""Determine the parent for a file_id (used as a key method)"""
450
return entry.parent_id
453
def name(entry, file_id):
454
"""Determine the name for a file_id (used as a key method)"""
460
def contents_sha1(tree, file_id):
461
"""Determine the sha1 of the file contents (used as a key method)."""
462
if file_id not in tree:
464
return tree.get_file_sha1(file_id)
467
def executable(tree, file_id):
468
"""Determine the executability of a file-id (used as a key method)."""
469
if file_id not in tree:
471
if tree.kind(file_id) != "file":
473
return tree.is_executable(file_id)
476
def kind(tree, file_id):
477
"""Determine the kind of a file-id (used as a key method)."""
478
if file_id not in tree:
480
return tree.kind(file_id)
483
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
484
"""Do a three-way test on a scalar.
485
Return "this", "other" or "conflict", depending whether a value wins.
487
key_base = key(base_tree, file_id)
488
key_other = key(other_tree, file_id)
489
#if base == other, either they all agree, or only THIS has changed.
490
if key_base == key_other:
492
key_this = key(this_tree, file_id)
493
if key_this not in (key_base, key_other):
495
# "Ambiguous clean merge"
496
elif key_this == key_other:
499
assert key_this == key_base
502
def merge_names(self, file_id):
503
"""Perform a merge on file_id names and parents"""
505
if file_id in tree.inventory:
506
return tree.inventory[file_id]
509
this_entry = get_entry(self.this_tree)
510
other_entry = get_entry(self.other_tree)
511
base_entry = get_entry(self.base_tree)
512
name_winner = self.scalar_three_way(this_entry, base_entry,
513
other_entry, file_id, self.name)
514
parent_id_winner = self.scalar_three_way(this_entry, base_entry,
515
other_entry, file_id,
517
if this_entry is None:
518
if name_winner == "this":
519
name_winner = "other"
520
if parent_id_winner == "this":
521
parent_id_winner = "other"
522
if name_winner == "this" and parent_id_winner == "this":
524
if name_winner == "conflict":
525
trans_id = self.tt.trans_id_file_id(file_id)
526
self._raw_conflicts.append(('name conflict', trans_id,
527
self.name(this_entry, file_id),
528
self.name(other_entry, file_id)))
529
if parent_id_winner == "conflict":
530
trans_id = self.tt.trans_id_file_id(file_id)
531
self._raw_conflicts.append(('parent conflict', trans_id,
532
self.parent(this_entry, file_id),
533
self.parent(other_entry, file_id)))
534
if other_entry is None:
535
# it doesn't matter whether the result was 'other' or
536
# 'conflict'-- if there's no 'other', we leave it alone.
538
# if we get here, name_winner and parent_winner are set to safe values.
539
winner_entry = {"this": this_entry, "other": other_entry,
540
"conflict": other_entry}
541
trans_id = self.tt.trans_id_file_id(file_id)
542
parent_id = winner_entry[parent_id_winner].parent_id
543
if parent_id is not None:
544
parent_trans_id = self.tt.trans_id_file_id(parent_id)
545
self.tt.adjust_path(winner_entry[name_winner].name,
546
parent_trans_id, trans_id)
548
def merge_contents(self, file_id):
549
"""Performa a merge on file_id contents."""
550
def contents_pair(tree):
551
if file_id not in tree:
553
kind = tree.kind(file_id)
555
contents = tree.get_file_sha1(file_id)
556
elif kind == "symlink":
557
contents = tree.get_symlink_target(file_id)
560
return kind, contents
562
def contents_conflict():
563
trans_id = self.tt.trans_id_file_id(file_id)
564
name = self.tt.final_name(trans_id)
565
parent_id = self.tt.final_parent(trans_id)
566
if file_id in self.this_tree.inventory:
567
self.tt.unversion_file(trans_id)
568
self.tt.delete_contents(trans_id)
569
file_group = self._dump_conflicts(name, parent_id, file_id,
571
self._raw_conflicts.append(('contents conflict', file_group))
573
# See SPOT run. run, SPOT, run.
574
# So we're not QUITE repeating ourselves; we do tricky things with
576
base_pair = contents_pair(self.base_tree)
577
other_pair = contents_pair(self.other_tree)
578
if base_pair == other_pair:
579
# OTHER introduced no changes
581
this_pair = contents_pair(self.this_tree)
582
if this_pair == other_pair:
583
# THIS and OTHER introduced the same changes
586
trans_id = self.tt.trans_id_file_id(file_id)
587
if this_pair == base_pair:
588
# only OTHER introduced changes
589
if file_id in self.this_tree:
590
# Remove any existing contents
591
self.tt.delete_contents(trans_id)
592
if file_id in self.other_tree:
593
# OTHER changed the file
594
create_by_entry(self.tt,
595
self.other_tree.inventory[file_id],
596
self.other_tree, trans_id)
597
if file_id not in self.this_tree.inventory:
598
self.tt.version_file(file_id, trans_id)
600
elif file_id in self.this_tree.inventory:
601
# OTHER deleted the file
602
self.tt.unversion_file(trans_id)
604
#BOTH THIS and OTHER introduced changes; scalar conflict
605
elif this_pair[0] == "file" and other_pair[0] == "file":
606
# THIS and OTHER are both files, so text merge. Either
607
# BASE is a file, or both converted to files, so at least we
608
# have agreement that output should be a file.
610
self.text_merge(file_id, trans_id)
612
return contents_conflict()
613
if file_id not in self.this_tree.inventory:
614
self.tt.version_file(file_id, trans_id)
616
self.tt.tree_kind(trans_id)
617
self.tt.delete_contents(trans_id)
622
# Scalar conflict, can't text merge. Dump conflicts
623
return contents_conflict()
625
def get_lines(self, tree, file_id):
626
"""Return the lines in a file, or an empty list."""
628
return tree.get_file(file_id).readlines()
632
def text_merge(self, file_id, trans_id):
633
"""Perform a three-way text merge on a file_id"""
634
# it's possible that we got here with base as a different type.
635
# if so, we just want two-way text conflicts.
636
if file_id in self.base_tree and \
637
self.base_tree.kind(file_id) == "file":
638
base_lines = self.get_lines(self.base_tree, file_id)
641
other_lines = self.get_lines(self.other_tree, file_id)
642
this_lines = self.get_lines(self.this_tree, file_id)
643
m3 = Merge3(base_lines, this_lines, other_lines)
644
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
645
if self.show_base is True:
646
base_marker = '|' * 7
650
def iter_merge3(retval):
651
retval["text_conflicts"] = False
652
for line in m3.merge_lines(name_a = "TREE",
653
name_b = "MERGE-SOURCE",
654
name_base = "BASE-REVISION",
655
start_marker=start_marker,
656
base_marker=base_marker,
657
reprocess=self.reprocess):
658
if line.startswith(start_marker):
659
retval["text_conflicts"] = True
660
yield line.replace(start_marker, '<' * 7)
664
merge3_iterator = iter_merge3(retval)
665
self.tt.create_file(merge3_iterator, trans_id)
666
if retval["text_conflicts"] is True:
667
self._raw_conflicts.append(('text conflict', trans_id))
668
name = self.tt.final_name(trans_id)
669
parent_id = self.tt.final_parent(trans_id)
670
file_group = self._dump_conflicts(name, parent_id, file_id,
671
this_lines, base_lines,
673
file_group.append(trans_id)
675
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
676
base_lines=None, other_lines=None, set_version=False,
678
"""Emit conflict files.
679
If this_lines, base_lines, or other_lines are omitted, they will be
680
determined automatically. If set_version is true, the .OTHER, .THIS
681
or .BASE (in that order) will be created as versioned files.
683
data = [('OTHER', self.other_tree, other_lines),
684
('THIS', self.this_tree, this_lines)]
686
data.append(('BASE', self.base_tree, base_lines))
689
for suffix, tree, lines in data:
691
trans_id = self._conflict_file(name, parent_id, tree, file_id,
693
file_group.append(trans_id)
694
if set_version and not versioned:
695
self.tt.version_file(file_id, trans_id)
699
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
701
"""Emit a single conflict file."""
702
name = name + '.' + suffix
703
trans_id = self.tt.create_path(name, parent_id)
704
entry = tree.inventory[file_id]
705
create_by_entry(self.tt, entry, tree, trans_id, lines)
708
def merge_executable(self, file_id, file_status):
709
"""Perform a merge on the execute bit."""
710
if file_status == "deleted":
712
trans_id = self.tt.trans_id_file_id(file_id)
714
if self.tt.final_kind(trans_id) != "file":
718
winner = self.scalar_three_way(self.this_tree, self.base_tree,
719
self.other_tree, file_id,
721
if winner == "conflict":
722
# There must be a None in here, if we have a conflict, but we
723
# need executability since file status was not deleted.
724
if self.other_tree.is_executable(file_id) is None:
729
if file_status == "modified":
730
executability = self.this_tree.is_executable(file_id)
731
if executability is not None:
732
trans_id = self.tt.trans_id_file_id(file_id)
733
self.tt.set_executability(executability, trans_id)
735
assert winner == "other"
736
if file_id in self.other_tree:
737
executability = self.other_tree.is_executable(file_id)
738
elif file_id in self.this_tree:
739
executability = self.this_tree.is_executable(file_id)
740
elif file_id in self.base_tree:
741
executability = self.base_tree.is_executable(file_id)
742
if executability is not None:
743
trans_id = self.tt.trans_id_file_id(file_id)
744
self.tt.set_executability(executability, trans_id)
746
def cook_conflicts(self, fs_conflicts):
747
"""Convert all conflicts into a form that doesn't depend on trans_id"""
748
from conflicts import Conflict
750
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
751
fp = FinalPaths(self.tt)
752
for conflict in self._raw_conflicts:
753
conflict_type = conflict[0]
754
if conflict_type in ('name conflict', 'parent conflict'):
755
trans_id = conflict[1]
756
conflict_args = conflict[2:]
757
if trans_id not in name_conflicts:
758
name_conflicts[trans_id] = {}
759
unique_add(name_conflicts[trans_id], conflict_type,
761
if conflict_type == 'contents conflict':
762
for trans_id in conflict[1]:
763
file_id = self.tt.final_file_id(trans_id)
764
if file_id is not None:
766
path = fp.get_path(trans_id)
767
for suffix in ('.BASE', '.THIS', '.OTHER'):
768
if path.endswith(suffix):
769
path = path[:-len(suffix)]
771
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
772
self.cooked_conflicts.append(c)
773
if conflict_type == 'text conflict':
774
trans_id = conflict[1]
775
path = fp.get_path(trans_id)
776
file_id = self.tt.final_file_id(trans_id)
777
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
778
self.cooked_conflicts.append(c)
780
for trans_id, conflicts in name_conflicts.iteritems():
782
this_parent, other_parent = conflicts['parent conflict']
783
assert this_parent != other_parent
785
this_parent = other_parent = \
786
self.tt.final_file_id(self.tt.final_parent(trans_id))
788
this_name, other_name = conflicts['name conflict']
789
assert this_name != other_name
791
this_name = other_name = self.tt.final_name(trans_id)
792
other_path = fp.get_path(trans_id)
793
if this_parent is not None:
795
fp.get_path(self.tt.trans_id_file_id(this_parent))
796
this_path = pathjoin(this_parent_path, this_name)
798
this_path = "<deleted>"
799
file_id = self.tt.final_file_id(trans_id)
800
c = Conflict.factory('path conflict', path=this_path,
801
conflict_path=other_path, file_id=file_id)
802
self.cooked_conflicts.append(c)
803
self.cooked_conflicts.sort(key=Conflict.sort_key)
806
class WeaveMerger(Merge3Merger):
807
"""Three-way tree merger, text weave merger."""
808
supports_reprocess = True
809
supports_show_base = False
811
def __init__(self, working_tree, this_tree, base_tree, other_tree,
812
interesting_ids=None, pb=DummyProgress(), pp=None,
814
self.this_revision_tree = self._get_revision_tree(this_tree)
815
self.other_revision_tree = self._get_revision_tree(other_tree)
816
super(WeaveMerger, self).__init__(working_tree, this_tree,
817
base_tree, other_tree,
818
interesting_ids=interesting_ids,
819
pb=pb, pp=pp, reprocess=reprocess)
821
def _get_revision_tree(self, tree):
822
"""Return a revision tree releated to this tree.
823
If the tree is a WorkingTree, the basis will be returned.
825
if getattr(tree, 'get_weave', False) is False:
826
# If we have a WorkingTree, try using the basis
827
return tree.branch.basis_tree()
831
def _check_file(self, file_id):
832
"""Check that the revision tree's version of the file matches."""
833
for tree, rt in ((self.this_tree, self.this_revision_tree),
834
(self.other_tree, self.other_revision_tree)):
837
if tree.get_file_sha1(file_id) != rt.get_file_sha1(file_id):
838
raise WorkingTreeNotRevision(self.this_tree)
840
def _merged_lines(self, file_id):
841
"""Generate the merged lines.
842
There is no distinction between lines that are meant to contain <<<<<<<
845
weave = self.this_revision_tree.get_weave(file_id)
846
this_revision_id = self.this_revision_tree.inventory[file_id].revision
847
other_revision_id = \
848
self.other_revision_tree.inventory[file_id].revision
849
wm = WeaveMerge(weave, this_revision_id, other_revision_id,
850
'<<<<<<< TREE\n', '>>>>>>> MERGE-SOURCE\n')
851
return wm.merge_lines(self.reprocess)
853
def text_merge(self, file_id, trans_id):
854
"""Perform a (weave) text merge for a given file and file-id.
855
If conflicts are encountered, .THIS and .OTHER files will be emitted,
856
and a conflict will be noted.
858
self._check_file(file_id)
859
lines, conflicts = self._merged_lines(file_id)
861
# Note we're checking whether the OUTPUT is binary in this case,
862
# because we don't want to get into weave merge guts.
863
check_text_lines(lines)
864
self.tt.create_file(lines, trans_id)
866
self._raw_conflicts.append(('text conflict', trans_id))
867
name = self.tt.final_name(trans_id)
868
parent_id = self.tt.final_parent(trans_id)
869
file_group = self._dump_conflicts(name, parent_id, file_id,
871
file_group.append(trans_id)
874
class Diff3Merger(Merge3Merger):
875
"""Three-way merger using external diff3 for text merging"""
876
def dump_file(self, temp_dir, name, tree, file_id):
877
out_path = pathjoin(temp_dir, name)
878
out_file = file(out_path, "wb")
879
in_file = tree.get_file(file_id)
884
def text_merge(self, file_id, trans_id):
885
"""Perform a diff3 merge using a specified file-id and trans-id.
886
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
887
will be dumped, and a will be conflict noted.
890
temp_dir = mkdtemp(prefix="bzr-")
892
new_file = pathjoin(temp_dir, "new")
893
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
894
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
895
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
896
status = bzrlib.patch.diff3(new_file, this, base, other)
897
if status not in (0, 1):
898
raise BzrError("Unhandled diff3 exit code")
899
self.tt.create_file(file(new_file, "rb"), trans_id)
901
name = self.tt.final_name(trans_id)
902
parent_id = self.tt.final_parent(trans_id)
903
self._dump_conflicts(name, parent_id, file_id)
904
self._raw_conflicts.append(('text conflict', trans_id))
909
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
911
merge_type=Merge3Merger,
912
interesting_ids=None,
916
interesting_files=None,
919
"""Primary interface for merging.
921
typical use is probably
922
'merge_inner(branch, branch.get_revision_tree(other_revision),
923
branch.get_revision_tree(base_revision))'
925
if this_tree is None:
926
warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
927
"bzrlib version 0.8.",
930
this_tree = this_branch.bzrdir.open_workingtree()
931
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
933
merger.backup_files = backup_files
934
merger.merge_type = merge_type
935
merger.interesting_ids = interesting_ids
936
merger.ignore_zero = ignore_zero
937
if interesting_files:
938
assert not interesting_ids, ('Only supply interesting_ids'
939
' or interesting_files')
940
merger._set_interesting_files(interesting_files)
941
merger.show_base = show_base
942
merger.reprocess = reprocess
943
merger.other_rev_id = other_rev_id
944
merger.other_basis = other_rev_id
945
return merger.do_merge()
948
merge_types = { "merge3": (Merge3Merger, "Native diff3-style merge"),
949
"diff3": (Diff3Merger, "Merge using external diff3"),
950
'weave': (WeaveMerger, "Weave-based merge")
954
def merge_type_help():
955
templ = '%s%%7s: %%s' % (' '*12)
956
lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
957
return '\n'.join(lines)