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,
94
assert this_tree is not None, "this_tree is required"
95
self.this_branch = this_branch
96
self.this_basis = this_branch.last_revision()
97
self.this_rev_id = None
98
self.this_tree = this_tree
99
self.this_revision_tree = None
100
self.this_basis_tree = None
101
self.other_tree = other_tree
102
self.other_branch = None
103
self.base_tree = base_tree
104
self.ignore_zero = False
105
self.backup_files = False
106
self.interesting_ids = None
107
self.show_base = False
108
self.reprocess = False
111
self.recurse = recurse
112
self.change_reporter = change_reporter
114
def revision_tree(self, revision_id):
115
return self.this_branch.repository.revision_tree(revision_id)
117
def ensure_revision_trees(self):
118
if self.this_revision_tree is None:
119
self.this_basis_tree = self.this_branch.repository.revision_tree(
121
if self.this_basis == self.this_rev_id:
122
self.this_revision_tree = self.this_basis_tree
124
if self.other_rev_id is None:
125
other_basis_tree = self.revision_tree(self.other_basis)
126
changes = other_basis_tree.changes_from(self.other_tree)
127
if changes.has_changed():
128
raise WorkingTreeNotRevision(self.this_tree)
129
other_rev_id = self.other_basis
130
self.other_tree = other_basis_tree
132
def file_revisions(self, file_id):
133
self.ensure_revision_trees()
134
def get_id(tree, file_id):
135
revision_id = tree.inventory[file_id].revision
136
assert revision_id is not None
138
if self.this_rev_id is None:
139
if self.this_basis_tree.get_file_sha1(file_id) != \
140
self.this_tree.get_file_sha1(file_id):
141
raise WorkingTreeNotRevision(self.this_tree)
143
trees = (self.this_basis_tree, self.other_tree)
144
return [get_id(tree, file_id) for tree in trees]
146
def check_basis(self, check_clean, require_commits=True):
147
if self.this_basis is None and require_commits is True:
148
raise BzrCommandError("This branch has no commits."
149
" (perhaps you would prefer 'bzr pull')")
152
if self.this_basis != self.this_rev_id:
153
raise BzrCommandError("Working tree has uncommitted changes.")
155
def compare_basis(self):
156
changes = self.this_tree.changes_from(self.this_tree.basis_tree())
157
if not changes.has_changed():
158
self.this_rev_id = self.this_basis
160
def set_interesting_files(self, file_list):
162
self._set_interesting_files(file_list)
163
except NotVersionedError, e:
164
raise BzrCommandError("%s is not a source file in any"
167
def _set_interesting_files(self, file_list):
168
"""Set the list of interesting ids from a list of files."""
169
if file_list is None:
170
self.interesting_ids = None
173
interesting_ids = set()
174
for path in file_list:
176
# TODO: jam 20070226 The trees are not locked at this time,
177
# wouldn't it make merge faster if it locks everything in the
178
# beginning? It locks at do_merge time, but this happens
180
for tree in (self.this_tree, self.base_tree, self.other_tree):
181
file_id = tree.path2id(path)
182
if file_id is not None:
183
interesting_ids.add(file_id)
186
raise NotVersionedError(path=path)
187
self.interesting_ids = interesting_ids
189
def set_pending(self):
190
if not self.base_is_ancestor:
192
if self.other_rev_id is None:
194
ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
195
if self.other_rev_id in ancestry:
197
self.this_tree.add_parent_tree((self.other_rev_id, self.other_tree))
199
def set_other(self, other_revision):
200
"""Set the revision and tree to merge from.
202
This sets the other_tree, other_rev_id, other_basis attributes.
204
:param other_revision: The [path, revision] list to merge from.
206
self.other_branch, self.other_tree = _get_tree(other_revision,
208
if other_revision[1] == -1:
209
self.other_rev_id = self.other_branch.last_revision()
210
if self.other_rev_id is None:
211
raise NoCommits(self.other_branch)
212
self.other_basis = self.other_rev_id
213
elif other_revision[1] is not None:
214
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
215
self.other_basis = self.other_rev_id
217
self.other_rev_id = None
218
self.other_basis = self.other_branch.last_revision()
219
if self.other_basis is None:
220
raise NoCommits(self.other_branch)
221
if self.other_branch.base != self.this_branch.base:
222
self.this_branch.fetch(self.other_branch,
223
last_revision=self.other_basis)
225
def set_other_revision(self, revision_id, other_branch):
226
"""Set 'other' based on a branch and revision id
228
:param revision_id: The revision to use for a tree
229
:param other_branch: The branch containing this tree
231
self.other_rev_id = revision_id
232
self.other_branch = other_branch
233
self.this_branch.fetch(other_branch, self.other_rev_id)
234
self.other_tree = self.revision_tree(revision_id)
235
self.other_basis = revision_id
238
self.set_base([None, None])
240
def set_base(self, base_revision):
241
"""Set the base revision to use for the merge.
243
:param base_revision: A 2-list containing a path and revision number.
245
mutter("doing merge() with no base_revision specified")
246
if base_revision == [None, None]:
248
pb = ui.ui_factory.nested_progress_bar()
250
this_repo = self.this_branch.repository
251
self.base_rev_id = common_ancestor(self.this_basis,
256
except NoCommonAncestor:
257
raise UnrelatedBranches()
258
self.base_tree = _get_revid_tree(self.this_branch, self.base_rev_id,
260
self.base_is_ancestor = True
262
base_branch, self.base_tree = _get_tree(base_revision)
263
if base_revision[1] == -1:
264
self.base_rev_id = base_branch.last_revision()
265
elif base_revision[1] is None:
266
self.base_rev_id = None
268
self.base_rev_id = base_branch.get_rev_id(base_revision[1])
269
if self.this_branch.base != base_branch.base:
270
self.this_branch.fetch(base_branch)
271
self.base_is_ancestor = is_ancestor(self.this_basis,
276
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
277
'other_tree': self.other_tree,
278
'interesting_ids': self.interesting_ids,
280
if self.merge_type.requires_base:
281
kwargs['base_tree'] = self.base_tree
282
if self.merge_type.supports_reprocess:
283
kwargs['reprocess'] = self.reprocess
285
raise BzrError("Conflict reduction is not supported for merge"
286
" type %s." % self.merge_type)
287
if self.merge_type.supports_show_base:
288
kwargs['show_base'] = self.show_base
290
raise BzrError("Showing base is not supported for this"
291
" merge type. %s" % self.merge_type)
292
self.this_tree.lock_tree_write()
293
if self.base_tree is not None:
294
self.base_tree.lock_read()
295
if self.other_tree is not None:
296
self.other_tree.lock_read()
298
merge = self.merge_type(pb=self._pb,
299
change_reporter=self.change_reporter,
302
if self.other_tree is not None:
303
self.other_tree.unlock()
304
if self.base_tree is not None:
305
self.base_tree.unlock()
306
self.this_tree.unlock()
307
if len(merge.cooked_conflicts) == 0:
308
if not self.ignore_zero:
309
note("All changes applied successfully.")
311
note("%d conflicts encountered." % len(merge.cooked_conflicts))
313
if self.recurse == 'down':
314
for path, entry in self.this_tree.iter_reference_entries():
315
sub_tree = self.this_tree.get_nested_tree(entry, path)
316
other_entry = self.other_tree.inventory[entry.file_id]
317
other_revision = self.other_tree.get_reference_revision(
319
if other_revision == sub_tree.last_revision():
321
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
322
sub_merge.merge_type = self.merge_type
323
other_branch = self.other_branch.reference_parent(
325
sub_merge.set_other_revision(other_revision, other_branch)
326
base_entry = self.base_tree.inventory[entry.file_id]
328
self.base_tree.get_reference_revision(base_entry)
329
sub_merge.base_tree = \
330
sub_tree.branch.repository.revision_tree(base_revision)
333
return len(merge.cooked_conflicts)
335
def regen_inventory(self, new_entries):
336
old_entries = self.this_tree.read_working_inventory()
340
for path, file_id in new_entries:
343
new_entries_map[file_id] = path
345
def id2path(file_id):
346
path = new_entries_map.get(file_id)
349
entry = old_entries[file_id]
350
if entry.parent_id is None:
352
return pathjoin(id2path(entry.parent_id), entry.name)
354
for file_id in old_entries:
355
entry = old_entries[file_id]
356
path = id2path(file_id)
357
if file_id in self.base_tree.inventory:
358
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
360
executable = getattr(entry, 'executable', False)
361
new_inventory[file_id] = (path, file_id, entry.parent_id,
362
entry.kind, executable)
364
by_path[path] = file_id
369
for path, file_id in new_entries:
371
del new_inventory[file_id]
374
new_path_list.append((path, file_id))
375
if file_id not in old_entries:
377
# Ensure no file is added before its parent
379
for path, file_id in new_path_list:
383
parent = by_path[os.path.dirname(path)]
384
abspath = pathjoin(self.this_tree.basedir, path)
385
kind = osutils.file_kind(abspath)
386
if file_id in self.base_tree.inventory:
387
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
390
new_inventory[file_id] = (path, file_id, parent, kind, executable)
391
by_path[path] = file_id
393
# Get a list in insertion order
394
new_inventory_list = new_inventory.values()
395
mutter ("""Inventory regeneration:
396
old length: %i insertions: %i deletions: %i new_length: %i"""\
397
% (len(old_entries), insertions, deletions,
398
len(new_inventory_list)))
399
assert len(new_inventory_list) == len(old_entries) + insertions\
401
new_inventory_list.sort()
402
return new_inventory_list
405
class Merge3Merger(object):
406
"""Three-way merger that uses the merge3 text merger"""
408
supports_reprocess = True
409
supports_show_base = True
410
history_based = False
412
def __init__(self, working_tree, this_tree, base_tree, other_tree,
413
interesting_ids=None, reprocess=False, show_base=False,
414
pb=DummyProgress(), pp=None, change_reporter=None):
415
"""Initialize the merger object and perform the merge."""
416
object.__init__(self)
417
self.this_tree = working_tree
418
self.this_tree.lock_tree_write()
419
self.base_tree = base_tree
420
self.base_tree.lock_read()
421
self.other_tree = other_tree
422
self.other_tree.lock_read()
423
self._raw_conflicts = []
424
self.cooked_conflicts = []
425
self.reprocess = reprocess
426
self.show_base = show_base
429
self.change_reporter = change_reporter
431
self.pp = ProgressPhase("Merge phase", 3, self.pb)
433
if interesting_ids is not None:
434
all_ids = interesting_ids
436
all_ids = set(base_tree)
437
all_ids.update(other_tree)
438
self.tt = TreeTransform(working_tree, self.pb)
441
child_pb = ui.ui_factory.nested_progress_bar()
443
for num, file_id in enumerate(all_ids):
444
child_pb.update('Preparing file merge', num, len(all_ids))
445
self.merge_names(file_id)
446
file_status = self.merge_contents(file_id)
447
self.merge_executable(file_id, file_status)
452
child_pb = ui.ui_factory.nested_progress_bar()
454
fs_conflicts = resolve_conflicts(self.tt, child_pb)
457
if change_reporter is not None:
458
from bzrlib import delta
459
delta.report_changes(self.tt._iter_changes(), change_reporter)
460
self.cook_conflicts(fs_conflicts)
461
for conflict in self.cooked_conflicts:
464
results = self.tt.apply()
465
self.write_modified(results)
467
working_tree.add_conflicts(self.cooked_conflicts)
468
except UnsupportedOperation:
472
self.other_tree.unlock()
473
self.base_tree.unlock()
474
self.this_tree.unlock()
479
self.tt.final_kind(self.tt.root)
481
self.tt.cancel_deletion(self.tt.root)
482
if self.tt.final_file_id(self.tt.root) is None:
483
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
485
if self.other_tree.inventory.root is None:
487
other_root_file_id = self.other_tree.inventory.root.file_id
488
other_root = self.tt.trans_id_file_id(other_root_file_id)
489
if other_root == self.tt.root:
492
self.tt.final_kind(other_root)
495
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
496
self.tt.cancel_creation(other_root)
497
self.tt.cancel_versioning(other_root)
499
def reparent_children(self, ie, target):
500
for thing, child in ie.children.iteritems():
501
trans_id = self.tt.trans_id_file_id(child.file_id)
502
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
504
def write_modified(self, results):
506
for path in results.modified_paths:
507
file_id = self.this_tree.path2id(self.this_tree.relpath(path))
510
hash = self.this_tree.get_file_sha1(file_id)
513
modified_hashes[file_id] = hash
514
self.this_tree.set_merge_modified(modified_hashes)
517
def parent(entry, file_id):
518
"""Determine the parent for a file_id (used as a key method)"""
521
return entry.parent_id
524
def name(entry, file_id):
525
"""Determine the name for a file_id (used as a key method)"""
531
def contents_sha1(tree, file_id):
532
"""Determine the sha1 of the file contents (used as a key method)."""
533
if file_id not in tree:
535
return tree.get_file_sha1(file_id)
538
def executable(tree, file_id):
539
"""Determine the executability of a file-id (used as a key method)."""
540
if file_id not in tree:
542
if tree.kind(file_id) != "file":
544
return tree.is_executable(file_id)
547
def kind(tree, file_id):
548
"""Determine the kind of a file-id (used as a key method)."""
549
if file_id not in tree:
551
return tree.kind(file_id)
554
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
555
"""Do a three-way test on a scalar.
556
Return "this", "other" or "conflict", depending whether a value wins.
558
key_base = key(base_tree, file_id)
559
key_other = key(other_tree, file_id)
560
#if base == other, either they all agree, or only THIS has changed.
561
if key_base == key_other:
563
key_this = key(this_tree, file_id)
564
if key_this not in (key_base, key_other):
566
# "Ambiguous clean merge"
567
elif key_this == key_other:
570
assert key_this == key_base
573
def merge_names(self, file_id):
574
"""Perform a merge on file_id names and parents"""
576
if file_id in tree.inventory:
577
return tree.inventory[file_id]
580
this_entry = get_entry(self.this_tree)
581
other_entry = get_entry(self.other_tree)
582
base_entry = get_entry(self.base_tree)
583
name_winner = self.scalar_three_way(this_entry, base_entry,
584
other_entry, file_id, self.name)
585
parent_id_winner = self.scalar_three_way(this_entry, base_entry,
586
other_entry, file_id,
588
if this_entry is None:
589
if name_winner == "this":
590
name_winner = "other"
591
if parent_id_winner == "this":
592
parent_id_winner = "other"
593
if name_winner == "this" and parent_id_winner == "this":
595
if name_winner == "conflict":
596
trans_id = self.tt.trans_id_file_id(file_id)
597
self._raw_conflicts.append(('name conflict', trans_id,
598
self.name(this_entry, file_id),
599
self.name(other_entry, file_id)))
600
if parent_id_winner == "conflict":
601
trans_id = self.tt.trans_id_file_id(file_id)
602
self._raw_conflicts.append(('parent conflict', trans_id,
603
self.parent(this_entry, file_id),
604
self.parent(other_entry, file_id)))
605
if other_entry is None:
606
# it doesn't matter whether the result was 'other' or
607
# 'conflict'-- if there's no 'other', we leave it alone.
609
# if we get here, name_winner and parent_winner are set to safe values.
610
winner_entry = {"this": this_entry, "other": other_entry,
611
"conflict": other_entry}
612
trans_id = self.tt.trans_id_file_id(file_id)
613
parent_id = winner_entry[parent_id_winner].parent_id
614
if parent_id is not None:
615
parent_trans_id = self.tt.trans_id_file_id(parent_id)
616
self.tt.adjust_path(winner_entry[name_winner].name,
617
parent_trans_id, trans_id)
619
def merge_contents(self, file_id):
620
"""Performa a merge on file_id contents."""
621
def contents_pair(tree):
622
if file_id not in tree:
624
kind = tree.kind(file_id)
626
contents = tree.get_file_sha1(file_id)
627
elif kind == "symlink":
628
contents = tree.get_symlink_target(file_id)
631
return kind, contents
633
def contents_conflict():
634
trans_id = self.tt.trans_id_file_id(file_id)
635
name = self.tt.final_name(trans_id)
636
parent_id = self.tt.final_parent(trans_id)
637
if file_id in self.this_tree.inventory:
638
self.tt.unversion_file(trans_id)
639
if file_id in self.this_tree:
640
self.tt.delete_contents(trans_id)
641
file_group = self._dump_conflicts(name, parent_id, file_id,
643
self._raw_conflicts.append(('contents conflict', file_group))
645
# See SPOT run. run, SPOT, run.
646
# So we're not QUITE repeating ourselves; we do tricky things with
648
base_pair = contents_pair(self.base_tree)
649
other_pair = contents_pair(self.other_tree)
650
if base_pair == other_pair:
651
# OTHER introduced no changes
653
this_pair = contents_pair(self.this_tree)
654
if this_pair == other_pair:
655
# THIS and OTHER introduced the same changes
658
trans_id = self.tt.trans_id_file_id(file_id)
659
if this_pair == base_pair:
660
# only OTHER introduced changes
661
if file_id in self.this_tree:
662
# Remove any existing contents
663
self.tt.delete_contents(trans_id)
664
if file_id in self.other_tree:
665
# OTHER changed the file
666
create_by_entry(self.tt,
667
self.other_tree.inventory[file_id],
668
self.other_tree, trans_id)
669
if file_id not in self.this_tree.inventory:
670
self.tt.version_file(file_id, trans_id)
672
elif file_id in self.this_tree.inventory:
673
# OTHER deleted the file
674
self.tt.unversion_file(trans_id)
676
#BOTH THIS and OTHER introduced changes; scalar conflict
677
elif this_pair[0] == "file" and other_pair[0] == "file":
678
# THIS and OTHER are both files, so text merge. Either
679
# BASE is a file, or both converted to files, so at least we
680
# have agreement that output should be a file.
682
self.text_merge(file_id, trans_id)
684
return contents_conflict()
685
if file_id not in self.this_tree.inventory:
686
self.tt.version_file(file_id, trans_id)
688
self.tt.tree_kind(trans_id)
689
self.tt.delete_contents(trans_id)
694
# Scalar conflict, can't text merge. Dump conflicts
695
return contents_conflict()
697
def get_lines(self, tree, file_id):
698
"""Return the lines in a file, or an empty list."""
700
return tree.get_file(file_id).readlines()
704
def text_merge(self, file_id, trans_id):
705
"""Perform a three-way text merge on a file_id"""
706
# it's possible that we got here with base as a different type.
707
# if so, we just want two-way text conflicts.
708
if file_id in self.base_tree and \
709
self.base_tree.kind(file_id) == "file":
710
base_lines = self.get_lines(self.base_tree, file_id)
713
other_lines = self.get_lines(self.other_tree, file_id)
714
this_lines = self.get_lines(self.this_tree, file_id)
715
m3 = Merge3(base_lines, this_lines, other_lines)
716
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
717
if self.show_base is True:
718
base_marker = '|' * 7
722
def iter_merge3(retval):
723
retval["text_conflicts"] = False
724
for line in m3.merge_lines(name_a = "TREE",
725
name_b = "MERGE-SOURCE",
726
name_base = "BASE-REVISION",
727
start_marker=start_marker,
728
base_marker=base_marker,
729
reprocess=self.reprocess):
730
if line.startswith(start_marker):
731
retval["text_conflicts"] = True
732
yield line.replace(start_marker, '<' * 7)
736
merge3_iterator = iter_merge3(retval)
737
self.tt.create_file(merge3_iterator, trans_id)
738
if retval["text_conflicts"] is True:
739
self._raw_conflicts.append(('text conflict', trans_id))
740
name = self.tt.final_name(trans_id)
741
parent_id = self.tt.final_parent(trans_id)
742
file_group = self._dump_conflicts(name, parent_id, file_id,
743
this_lines, base_lines,
745
file_group.append(trans_id)
747
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
748
base_lines=None, other_lines=None, set_version=False,
750
"""Emit conflict files.
751
If this_lines, base_lines, or other_lines are omitted, they will be
752
determined automatically. If set_version is true, the .OTHER, .THIS
753
or .BASE (in that order) will be created as versioned files.
755
data = [('OTHER', self.other_tree, other_lines),
756
('THIS', self.this_tree, this_lines)]
758
data.append(('BASE', self.base_tree, base_lines))
761
for suffix, tree, lines in data:
763
trans_id = self._conflict_file(name, parent_id, tree, file_id,
765
file_group.append(trans_id)
766
if set_version and not versioned:
767
self.tt.version_file(file_id, trans_id)
771
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
773
"""Emit a single conflict file."""
774
name = name + '.' + suffix
775
trans_id = self.tt.create_path(name, parent_id)
776
entry = tree.inventory[file_id]
777
create_by_entry(self.tt, entry, tree, trans_id, lines)
780
def merge_executable(self, file_id, file_status):
781
"""Perform a merge on the execute bit."""
782
if file_status == "deleted":
784
trans_id = self.tt.trans_id_file_id(file_id)
786
if self.tt.final_kind(trans_id) != "file":
790
winner = self.scalar_three_way(self.this_tree, self.base_tree,
791
self.other_tree, file_id,
793
if winner == "conflict":
794
# There must be a None in here, if we have a conflict, but we
795
# need executability since file status was not deleted.
796
if self.executable(self.other_tree, file_id) is None:
801
if file_status == "modified":
802
executability = self.this_tree.is_executable(file_id)
803
if executability is not None:
804
trans_id = self.tt.trans_id_file_id(file_id)
805
self.tt.set_executability(executability, trans_id)
807
assert winner == "other"
808
if file_id in self.other_tree:
809
executability = self.other_tree.is_executable(file_id)
810
elif file_id in self.this_tree:
811
executability = self.this_tree.is_executable(file_id)
812
elif file_id in self.base_tree:
813
executability = self.base_tree.is_executable(file_id)
814
if executability is not None:
815
trans_id = self.tt.trans_id_file_id(file_id)
816
self.tt.set_executability(executability, trans_id)
818
def cook_conflicts(self, fs_conflicts):
819
"""Convert all conflicts into a form that doesn't depend on trans_id"""
820
from conflicts import Conflict
822
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
823
fp = FinalPaths(self.tt)
824
for conflict in self._raw_conflicts:
825
conflict_type = conflict[0]
826
if conflict_type in ('name conflict', 'parent conflict'):
827
trans_id = conflict[1]
828
conflict_args = conflict[2:]
829
if trans_id not in name_conflicts:
830
name_conflicts[trans_id] = {}
831
unique_add(name_conflicts[trans_id], conflict_type,
833
if conflict_type == 'contents conflict':
834
for trans_id in conflict[1]:
835
file_id = self.tt.final_file_id(trans_id)
836
if file_id is not None:
838
path = fp.get_path(trans_id)
839
for suffix in ('.BASE', '.THIS', '.OTHER'):
840
if path.endswith(suffix):
841
path = path[:-len(suffix)]
843
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
844
self.cooked_conflicts.append(c)
845
if conflict_type == 'text conflict':
846
trans_id = conflict[1]
847
path = fp.get_path(trans_id)
848
file_id = self.tt.final_file_id(trans_id)
849
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
850
self.cooked_conflicts.append(c)
852
for trans_id, conflicts in name_conflicts.iteritems():
854
this_parent, other_parent = conflicts['parent conflict']
855
assert this_parent != other_parent
857
this_parent = other_parent = \
858
self.tt.final_file_id(self.tt.final_parent(trans_id))
860
this_name, other_name = conflicts['name conflict']
861
assert this_name != other_name
863
this_name = other_name = self.tt.final_name(trans_id)
864
other_path = fp.get_path(trans_id)
865
if this_parent is not None:
867
fp.get_path(self.tt.trans_id_file_id(this_parent))
868
this_path = pathjoin(this_parent_path, this_name)
870
this_path = "<deleted>"
871
file_id = self.tt.final_file_id(trans_id)
872
c = Conflict.factory('path conflict', path=this_path,
873
conflict_path=other_path, file_id=file_id)
874
self.cooked_conflicts.append(c)
875
self.cooked_conflicts.sort(key=Conflict.sort_key)
878
class WeaveMerger(Merge3Merger):
879
"""Three-way tree merger, text weave merger."""
880
supports_reprocess = True
881
supports_show_base = False
883
def __init__(self, working_tree, this_tree, base_tree, other_tree,
884
interesting_ids=None, pb=DummyProgress(), pp=None,
885
reprocess=False, change_reporter=None):
886
self.this_revision_tree = self._get_revision_tree(this_tree)
887
self.other_revision_tree = self._get_revision_tree(other_tree)
888
super(WeaveMerger, self).__init__(working_tree, this_tree,
889
base_tree, other_tree,
890
interesting_ids=interesting_ids,
891
pb=pb, pp=pp, reprocess=reprocess,
892
change_reporter=change_reporter)
894
def _get_revision_tree(self, tree):
895
"""Return a revision tree related to this tree.
896
If the tree is a WorkingTree, the basis will be returned.
898
if getattr(tree, 'get_weave', False) is False:
899
# If we have a WorkingTree, try using the basis
900
return tree.branch.basis_tree()
904
def _check_file(self, file_id):
905
"""Check that the revision tree's version of the file matches."""
906
for tree, rt in ((self.this_tree, self.this_revision_tree),
907
(self.other_tree, self.other_revision_tree)):
910
if tree.get_file_sha1(file_id) != rt.get_file_sha1(file_id):
911
raise WorkingTreeNotRevision(self.this_tree)
913
def _merged_lines(self, file_id):
914
"""Generate the merged lines.
915
There is no distinction between lines that are meant to contain <<<<<<<
918
weave = self.this_revision_tree.get_weave(file_id)
919
this_revision_id = self.this_revision_tree.inventory[file_id].revision
920
other_revision_id = \
921
self.other_revision_tree.inventory[file_id].revision
922
wm = WeaveMerge(weave, this_revision_id, other_revision_id,
923
'<<<<<<< TREE\n', '>>>>>>> MERGE-SOURCE\n')
924
return wm.merge_lines(self.reprocess)
926
def text_merge(self, file_id, trans_id):
927
"""Perform a (weave) text merge for a given file and file-id.
928
If conflicts are encountered, .THIS and .OTHER files will be emitted,
929
and a conflict will be noted.
931
self._check_file(file_id)
932
lines, conflicts = self._merged_lines(file_id)
934
# Note we're checking whether the OUTPUT is binary in this case,
935
# because we don't want to get into weave merge guts.
936
check_text_lines(lines)
937
self.tt.create_file(lines, trans_id)
939
self._raw_conflicts.append(('text conflict', trans_id))
940
name = self.tt.final_name(trans_id)
941
parent_id = self.tt.final_parent(trans_id)
942
file_group = self._dump_conflicts(name, parent_id, file_id,
944
file_group.append(trans_id)
947
class Diff3Merger(Merge3Merger):
948
"""Three-way merger using external diff3 for text merging"""
950
def dump_file(self, temp_dir, name, tree, file_id):
951
out_path = pathjoin(temp_dir, name)
952
out_file = open(out_path, "wb")
954
in_file = tree.get_file(file_id)
961
def text_merge(self, file_id, trans_id):
962
"""Perform a diff3 merge using a specified file-id and trans-id.
963
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
964
will be dumped, and a will be conflict noted.
967
temp_dir = osutils.mkdtemp(prefix="bzr-")
969
new_file = pathjoin(temp_dir, "new")
970
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
971
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
972
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
973
status = bzrlib.patch.diff3(new_file, this, base, other)
974
if status not in (0, 1):
975
raise BzrError("Unhandled diff3 exit code")
976
f = open(new_file, 'rb')
978
self.tt.create_file(f, trans_id)
982
name = self.tt.final_name(trans_id)
983
parent_id = self.tt.final_parent(trans_id)
984
self._dump_conflicts(name, parent_id, file_id)
985
self._raw_conflicts.append(('text conflict', trans_id))
987
osutils.rmtree(temp_dir)
990
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
992
merge_type=Merge3Merger,
993
interesting_ids=None,
997
interesting_files=None,
1000
change_reporter=None):
1001
"""Primary interface for merging.
1003
typical use is probably
1004
'merge_inner(branch, branch.get_revision_tree(other_revision),
1005
branch.get_revision_tree(base_revision))'
1007
if this_tree is None:
1008
warnings.warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
1009
"bzrlib version 0.8.",
1012
this_tree = this_branch.bzrdir.open_workingtree()
1013
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1014
pb=pb, change_reporter=change_reporter)
1015
merger.backup_files = backup_files
1016
merger.merge_type = merge_type
1017
merger.interesting_ids = interesting_ids
1018
merger.ignore_zero = ignore_zero
1019
if interesting_files:
1020
assert not interesting_ids, ('Only supply interesting_ids'
1021
' or interesting_files')
1022
merger._set_interesting_files(interesting_files)
1023
merger.show_base = show_base
1024
merger.reprocess = reprocess
1025
merger.other_rev_id = other_rev_id
1026
merger.other_basis = other_rev_id
1027
return merger.do_merge()
1029
def get_merge_type_registry():
1030
"""Merge type registry is in bzrlib.option to avoid circular imports.
1032
This method provides a sanctioned way to retrieve it.
1034
from bzrlib import option
1035
return option._merge_type_registry