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,
301
if self.recurse == 'down':
302
for path, file_id in self.this_tree.iter_references():
303
sub_tree = self.this_tree.get_nested_tree(file_id, path)
304
other_revision = self.other_tree.get_reference_revision(
306
if other_revision == sub_tree.last_revision():
308
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
309
sub_merge.merge_type = self.merge_type
310
relpath = self.this_tree.relpath(path)
311
other_branch = self.other_branch.reference_parent(file_id, relpath)
312
sub_merge.set_other_revision(other_revision, other_branch)
313
base_revision = self.base_tree.get_reference_revision(file_id)
314
sub_merge.base_tree = \
315
sub_tree.branch.repository.revision_tree(base_revision)
319
if self.other_tree is not None:
320
self.other_tree.unlock()
321
if self.base_tree is not None:
322
self.base_tree.unlock()
323
self.this_tree.unlock()
324
if len(merge.cooked_conflicts) == 0:
325
if not self.ignore_zero:
326
note("All changes applied successfully.")
328
note("%d conflicts encountered." % len(merge.cooked_conflicts))
330
return len(merge.cooked_conflicts)
332
def regen_inventory(self, new_entries):
333
old_entries = self.this_tree.read_working_inventory()
337
for path, file_id in new_entries:
340
new_entries_map[file_id] = path
342
def id2path(file_id):
343
path = new_entries_map.get(file_id)
346
entry = old_entries[file_id]
347
if entry.parent_id is None:
349
return pathjoin(id2path(entry.parent_id), entry.name)
351
for file_id in old_entries:
352
entry = old_entries[file_id]
353
path = id2path(file_id)
354
if file_id in self.base_tree.inventory:
355
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
357
executable = getattr(entry, 'executable', False)
358
new_inventory[file_id] = (path, file_id, entry.parent_id,
359
entry.kind, executable)
361
by_path[path] = file_id
366
for path, file_id in new_entries:
368
del new_inventory[file_id]
371
new_path_list.append((path, file_id))
372
if file_id not in old_entries:
374
# Ensure no file is added before its parent
376
for path, file_id in new_path_list:
380
parent = by_path[os.path.dirname(path)]
381
abspath = pathjoin(self.this_tree.basedir, path)
382
kind = osutils.file_kind(abspath)
383
if file_id in self.base_tree.inventory:
384
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
387
new_inventory[file_id] = (path, file_id, parent, kind, executable)
388
by_path[path] = file_id
390
# Get a list in insertion order
391
new_inventory_list = new_inventory.values()
392
mutter ("""Inventory regeneration:
393
old length: %i insertions: %i deletions: %i new_length: %i"""\
394
% (len(old_entries), insertions, deletions,
395
len(new_inventory_list)))
396
assert len(new_inventory_list) == len(old_entries) + insertions\
398
new_inventory_list.sort()
399
return new_inventory_list
402
class Merge3Merger(object):
403
"""Three-way merger that uses the merge3 text merger"""
405
supports_reprocess = True
406
supports_show_base = True
407
history_based = False
409
def __init__(self, working_tree, this_tree, base_tree, other_tree,
410
interesting_ids=None, reprocess=False, show_base=False,
411
pb=DummyProgress(), pp=None, change_reporter=None):
412
"""Initialize the merger object and perform the merge."""
413
object.__init__(self)
414
self.this_tree = working_tree
415
self.this_tree.lock_tree_write()
416
self.base_tree = base_tree
417
self.base_tree.lock_read()
418
self.other_tree = other_tree
419
self.other_tree.lock_read()
420
self._raw_conflicts = []
421
self.cooked_conflicts = []
422
self.reprocess = reprocess
423
self.show_base = show_base
426
self.change_reporter = change_reporter
428
self.pp = ProgressPhase("Merge phase", 3, self.pb)
430
if interesting_ids is not None:
431
all_ids = interesting_ids
433
all_ids = set(base_tree)
434
all_ids.update(other_tree)
435
self.tt = TreeTransform(working_tree, self.pb)
438
child_pb = ui.ui_factory.nested_progress_bar()
440
for num, file_id in enumerate(all_ids):
441
child_pb.update('Preparing file merge', num, len(all_ids))
442
self.merge_names(file_id)
443
file_status = self.merge_contents(file_id)
444
self.merge_executable(file_id, file_status)
449
child_pb = ui.ui_factory.nested_progress_bar()
451
fs_conflicts = resolve_conflicts(self.tt, child_pb)
454
if change_reporter is not None:
455
from bzrlib import delta
456
delta.report_changes(self.tt._iter_changes(), change_reporter)
457
self.cook_conflicts(fs_conflicts)
458
for conflict in self.cooked_conflicts:
461
results = self.tt.apply()
462
self.write_modified(results)
464
working_tree.add_conflicts(self.cooked_conflicts)
465
except UnsupportedOperation:
469
self.other_tree.unlock()
470
self.base_tree.unlock()
471
self.this_tree.unlock()
476
self.tt.final_kind(self.tt.root)
478
self.tt.cancel_deletion(self.tt.root)
479
if self.tt.final_file_id(self.tt.root) is None:
480
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
482
if self.other_tree.inventory.root is None:
484
other_root_file_id = self.other_tree.inventory.root.file_id
485
other_root = self.tt.trans_id_file_id(other_root_file_id)
486
if other_root == self.tt.root:
489
self.tt.final_kind(other_root)
492
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
493
self.tt.cancel_creation(other_root)
494
self.tt.cancel_versioning(other_root)
496
def reparent_children(self, ie, target):
497
for thing, child in ie.children.iteritems():
498
trans_id = self.tt.trans_id_file_id(child.file_id)
499
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
501
def write_modified(self, results):
503
for path in results.modified_paths:
504
file_id = self.this_tree.path2id(self.this_tree.relpath(path))
507
hash = self.this_tree.get_file_sha1(file_id)
510
modified_hashes[file_id] = hash
511
self.this_tree.set_merge_modified(modified_hashes)
514
def parent(entry, file_id):
515
"""Determine the parent for a file_id (used as a key method)"""
518
return entry.parent_id
521
def name(entry, file_id):
522
"""Determine the name for a file_id (used as a key method)"""
528
def contents_sha1(tree, file_id):
529
"""Determine the sha1 of the file contents (used as a key method)."""
530
if file_id not in tree:
532
return tree.get_file_sha1(file_id)
535
def executable(tree, file_id):
536
"""Determine the executability of a file-id (used as a key method)."""
537
if file_id not in tree:
539
if tree.kind(file_id) != "file":
541
return tree.is_executable(file_id)
544
def kind(tree, file_id):
545
"""Determine the kind of a file-id (used as a key method)."""
546
if file_id not in tree:
548
return tree.kind(file_id)
551
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
552
"""Do a three-way test on a scalar.
553
Return "this", "other" or "conflict", depending whether a value wins.
555
key_base = key(base_tree, file_id)
556
key_other = key(other_tree, file_id)
557
#if base == other, either they all agree, or only THIS has changed.
558
if key_base == key_other:
560
key_this = key(this_tree, file_id)
561
if key_this not in (key_base, key_other):
563
# "Ambiguous clean merge"
564
elif key_this == key_other:
567
assert key_this == key_base
570
def merge_names(self, file_id):
571
"""Perform a merge on file_id names and parents"""
573
if file_id in tree.inventory:
574
return tree.inventory[file_id]
577
this_entry = get_entry(self.this_tree)
578
other_entry = get_entry(self.other_tree)
579
base_entry = get_entry(self.base_tree)
580
name_winner = self.scalar_three_way(this_entry, base_entry,
581
other_entry, file_id, self.name)
582
parent_id_winner = self.scalar_three_way(this_entry, base_entry,
583
other_entry, file_id,
585
if this_entry is None:
586
if name_winner == "this":
587
name_winner = "other"
588
if parent_id_winner == "this":
589
parent_id_winner = "other"
590
if name_winner == "this" and parent_id_winner == "this":
592
if name_winner == "conflict":
593
trans_id = self.tt.trans_id_file_id(file_id)
594
self._raw_conflicts.append(('name conflict', trans_id,
595
self.name(this_entry, file_id),
596
self.name(other_entry, file_id)))
597
if parent_id_winner == "conflict":
598
trans_id = self.tt.trans_id_file_id(file_id)
599
self._raw_conflicts.append(('parent conflict', trans_id,
600
self.parent(this_entry, file_id),
601
self.parent(other_entry, file_id)))
602
if other_entry is None:
603
# it doesn't matter whether the result was 'other' or
604
# 'conflict'-- if there's no 'other', we leave it alone.
606
# if we get here, name_winner and parent_winner are set to safe values.
607
winner_entry = {"this": this_entry, "other": other_entry,
608
"conflict": other_entry}
609
trans_id = self.tt.trans_id_file_id(file_id)
610
parent_id = winner_entry[parent_id_winner].parent_id
611
if parent_id is not None:
612
parent_trans_id = self.tt.trans_id_file_id(parent_id)
613
self.tt.adjust_path(winner_entry[name_winner].name,
614
parent_trans_id, trans_id)
616
def merge_contents(self, file_id):
617
"""Performa a merge on file_id contents."""
618
def contents_pair(tree):
619
if file_id not in tree:
621
kind = tree.kind(file_id)
623
contents = tree.get_file_sha1(file_id)
624
elif kind == "symlink":
625
contents = tree.get_symlink_target(file_id)
628
return kind, contents
630
def contents_conflict():
631
trans_id = self.tt.trans_id_file_id(file_id)
632
name = self.tt.final_name(trans_id)
633
parent_id = self.tt.final_parent(trans_id)
634
if file_id in self.this_tree.inventory:
635
self.tt.unversion_file(trans_id)
636
if file_id in self.this_tree:
637
self.tt.delete_contents(trans_id)
638
file_group = self._dump_conflicts(name, parent_id, file_id,
640
self._raw_conflicts.append(('contents conflict', file_group))
642
# See SPOT run. run, SPOT, run.
643
# So we're not QUITE repeating ourselves; we do tricky things with
645
base_pair = contents_pair(self.base_tree)
646
other_pair = contents_pair(self.other_tree)
647
if base_pair == other_pair:
648
# OTHER introduced no changes
650
this_pair = contents_pair(self.this_tree)
651
if this_pair == other_pair:
652
# THIS and OTHER introduced the same changes
655
trans_id = self.tt.trans_id_file_id(file_id)
656
if this_pair == base_pair:
657
# only OTHER introduced changes
658
if file_id in self.this_tree:
659
# Remove any existing contents
660
self.tt.delete_contents(trans_id)
661
if file_id in self.other_tree:
662
# OTHER changed the file
663
create_by_entry(self.tt,
664
self.other_tree.inventory[file_id],
665
self.other_tree, trans_id)
666
if file_id not in self.this_tree.inventory:
667
self.tt.version_file(file_id, trans_id)
669
elif file_id in self.this_tree.inventory:
670
# OTHER deleted the file
671
self.tt.unversion_file(trans_id)
673
#BOTH THIS and OTHER introduced changes; scalar conflict
674
elif this_pair[0] == "file" and other_pair[0] == "file":
675
# THIS and OTHER are both files, so text merge. Either
676
# BASE is a file, or both converted to files, so at least we
677
# have agreement that output should be a file.
679
self.text_merge(file_id, trans_id)
681
return contents_conflict()
682
if file_id not in self.this_tree.inventory:
683
self.tt.version_file(file_id, trans_id)
685
self.tt.tree_kind(trans_id)
686
self.tt.delete_contents(trans_id)
691
# Scalar conflict, can't text merge. Dump conflicts
692
return contents_conflict()
694
def get_lines(self, tree, file_id):
695
"""Return the lines in a file, or an empty list."""
697
return tree.get_file(file_id).readlines()
701
def text_merge(self, file_id, trans_id):
702
"""Perform a three-way text merge on a file_id"""
703
# it's possible that we got here with base as a different type.
704
# if so, we just want two-way text conflicts.
705
if file_id in self.base_tree and \
706
self.base_tree.kind(file_id) == "file":
707
base_lines = self.get_lines(self.base_tree, file_id)
710
other_lines = self.get_lines(self.other_tree, file_id)
711
this_lines = self.get_lines(self.this_tree, file_id)
712
m3 = Merge3(base_lines, this_lines, other_lines)
713
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
714
if self.show_base is True:
715
base_marker = '|' * 7
719
def iter_merge3(retval):
720
retval["text_conflicts"] = False
721
for line in m3.merge_lines(name_a = "TREE",
722
name_b = "MERGE-SOURCE",
723
name_base = "BASE-REVISION",
724
start_marker=start_marker,
725
base_marker=base_marker,
726
reprocess=self.reprocess):
727
if line.startswith(start_marker):
728
retval["text_conflicts"] = True
729
yield line.replace(start_marker, '<' * 7)
733
merge3_iterator = iter_merge3(retval)
734
self.tt.create_file(merge3_iterator, trans_id)
735
if retval["text_conflicts"] is True:
736
self._raw_conflicts.append(('text conflict', trans_id))
737
name = self.tt.final_name(trans_id)
738
parent_id = self.tt.final_parent(trans_id)
739
file_group = self._dump_conflicts(name, parent_id, file_id,
740
this_lines, base_lines,
742
file_group.append(trans_id)
744
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
745
base_lines=None, other_lines=None, set_version=False,
747
"""Emit conflict files.
748
If this_lines, base_lines, or other_lines are omitted, they will be
749
determined automatically. If set_version is true, the .OTHER, .THIS
750
or .BASE (in that order) will be created as versioned files.
752
data = [('OTHER', self.other_tree, other_lines),
753
('THIS', self.this_tree, this_lines)]
755
data.append(('BASE', self.base_tree, base_lines))
758
for suffix, tree, lines in data:
760
trans_id = self._conflict_file(name, parent_id, tree, file_id,
762
file_group.append(trans_id)
763
if set_version and not versioned:
764
self.tt.version_file(file_id, trans_id)
768
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
770
"""Emit a single conflict file."""
771
name = name + '.' + suffix
772
trans_id = self.tt.create_path(name, parent_id)
773
entry = tree.inventory[file_id]
774
create_by_entry(self.tt, entry, tree, trans_id, lines)
777
def merge_executable(self, file_id, file_status):
778
"""Perform a merge on the execute bit."""
779
if file_status == "deleted":
781
trans_id = self.tt.trans_id_file_id(file_id)
783
if self.tt.final_kind(trans_id) != "file":
787
winner = self.scalar_three_way(self.this_tree, self.base_tree,
788
self.other_tree, file_id,
790
if winner == "conflict":
791
# There must be a None in here, if we have a conflict, but we
792
# need executability since file status was not deleted.
793
if self.executable(self.other_tree, file_id) is None:
798
if file_status == "modified":
799
executability = self.this_tree.is_executable(file_id)
800
if executability is not None:
801
trans_id = self.tt.trans_id_file_id(file_id)
802
self.tt.set_executability(executability, trans_id)
804
assert winner == "other"
805
if file_id in self.other_tree:
806
executability = self.other_tree.is_executable(file_id)
807
elif file_id in self.this_tree:
808
executability = self.this_tree.is_executable(file_id)
809
elif file_id in self.base_tree:
810
executability = self.base_tree.is_executable(file_id)
811
if executability is not None:
812
trans_id = self.tt.trans_id_file_id(file_id)
813
self.tt.set_executability(executability, trans_id)
815
def cook_conflicts(self, fs_conflicts):
816
"""Convert all conflicts into a form that doesn't depend on trans_id"""
817
from conflicts import Conflict
819
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
820
fp = FinalPaths(self.tt)
821
for conflict in self._raw_conflicts:
822
conflict_type = conflict[0]
823
if conflict_type in ('name conflict', 'parent conflict'):
824
trans_id = conflict[1]
825
conflict_args = conflict[2:]
826
if trans_id not in name_conflicts:
827
name_conflicts[trans_id] = {}
828
unique_add(name_conflicts[trans_id], conflict_type,
830
if conflict_type == 'contents conflict':
831
for trans_id in conflict[1]:
832
file_id = self.tt.final_file_id(trans_id)
833
if file_id is not None:
835
path = fp.get_path(trans_id)
836
for suffix in ('.BASE', '.THIS', '.OTHER'):
837
if path.endswith(suffix):
838
path = path[:-len(suffix)]
840
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
841
self.cooked_conflicts.append(c)
842
if conflict_type == 'text conflict':
843
trans_id = conflict[1]
844
path = fp.get_path(trans_id)
845
file_id = self.tt.final_file_id(trans_id)
846
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
847
self.cooked_conflicts.append(c)
849
for trans_id, conflicts in name_conflicts.iteritems():
851
this_parent, other_parent = conflicts['parent conflict']
852
assert this_parent != other_parent
854
this_parent = other_parent = \
855
self.tt.final_file_id(self.tt.final_parent(trans_id))
857
this_name, other_name = conflicts['name conflict']
858
assert this_name != other_name
860
this_name = other_name = self.tt.final_name(trans_id)
861
other_path = fp.get_path(trans_id)
862
if this_parent is not None:
864
fp.get_path(self.tt.trans_id_file_id(this_parent))
865
this_path = pathjoin(this_parent_path, this_name)
867
this_path = "<deleted>"
868
file_id = self.tt.final_file_id(trans_id)
869
c = Conflict.factory('path conflict', path=this_path,
870
conflict_path=other_path, file_id=file_id)
871
self.cooked_conflicts.append(c)
872
self.cooked_conflicts.sort(key=Conflict.sort_key)
875
class WeaveMerger(Merge3Merger):
876
"""Three-way tree merger, text weave merger."""
877
supports_reprocess = True
878
supports_show_base = False
880
def __init__(self, working_tree, this_tree, base_tree, other_tree,
881
interesting_ids=None, pb=DummyProgress(), pp=None,
882
reprocess=False, change_reporter=None):
883
self.this_revision_tree = self._get_revision_tree(this_tree)
884
self.other_revision_tree = self._get_revision_tree(other_tree)
885
super(WeaveMerger, self).__init__(working_tree, this_tree,
886
base_tree, other_tree,
887
interesting_ids=interesting_ids,
888
pb=pb, pp=pp, reprocess=reprocess,
889
change_reporter=change_reporter)
891
def _get_revision_tree(self, tree):
892
"""Return a revision tree related to this tree.
893
If the tree is a WorkingTree, the basis will be returned.
895
if getattr(tree, 'get_weave', False) is False:
896
# If we have a WorkingTree, try using the basis
897
return tree.branch.basis_tree()
901
def _check_file(self, file_id):
902
"""Check that the revision tree's version of the file matches."""
903
for tree, rt in ((self.this_tree, self.this_revision_tree),
904
(self.other_tree, self.other_revision_tree)):
907
if tree.get_file_sha1(file_id) != rt.get_file_sha1(file_id):
908
raise WorkingTreeNotRevision(self.this_tree)
910
def _merged_lines(self, file_id):
911
"""Generate the merged lines.
912
There is no distinction between lines that are meant to contain <<<<<<<
915
weave = self.this_revision_tree.get_weave(file_id)
916
this_revision_id = self.this_revision_tree.inventory[file_id].revision
917
other_revision_id = \
918
self.other_revision_tree.inventory[file_id].revision
919
wm = WeaveMerge(weave, this_revision_id, other_revision_id,
920
'<<<<<<< TREE\n', '>>>>>>> MERGE-SOURCE\n')
921
return wm.merge_lines(self.reprocess)
923
def text_merge(self, file_id, trans_id):
924
"""Perform a (weave) text merge for a given file and file-id.
925
If conflicts are encountered, .THIS and .OTHER files will be emitted,
926
and a conflict will be noted.
928
self._check_file(file_id)
929
lines, conflicts = self._merged_lines(file_id)
931
# Note we're checking whether the OUTPUT is binary in this case,
932
# because we don't want to get into weave merge guts.
933
check_text_lines(lines)
934
self.tt.create_file(lines, trans_id)
936
self._raw_conflicts.append(('text conflict', trans_id))
937
name = self.tt.final_name(trans_id)
938
parent_id = self.tt.final_parent(trans_id)
939
file_group = self._dump_conflicts(name, parent_id, file_id,
941
file_group.append(trans_id)
944
class Diff3Merger(Merge3Merger):
945
"""Three-way merger using external diff3 for text merging"""
947
def dump_file(self, temp_dir, name, tree, file_id):
948
out_path = pathjoin(temp_dir, name)
949
out_file = open(out_path, "wb")
951
in_file = tree.get_file(file_id)
958
def text_merge(self, file_id, trans_id):
959
"""Perform a diff3 merge using a specified file-id and trans-id.
960
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
961
will be dumped, and a will be conflict noted.
964
temp_dir = osutils.mkdtemp(prefix="bzr-")
966
new_file = pathjoin(temp_dir, "new")
967
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
968
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
969
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
970
status = bzrlib.patch.diff3(new_file, this, base, other)
971
if status not in (0, 1):
972
raise BzrError("Unhandled diff3 exit code")
973
f = open(new_file, 'rb')
975
self.tt.create_file(f, trans_id)
979
name = self.tt.final_name(trans_id)
980
parent_id = self.tt.final_parent(trans_id)
981
self._dump_conflicts(name, parent_id, file_id)
982
self._raw_conflicts.append(('text conflict', trans_id))
984
osutils.rmtree(temp_dir)
987
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
989
merge_type=Merge3Merger,
990
interesting_ids=None,
994
interesting_files=None,
997
change_reporter=None):
998
"""Primary interface for merging.
1000
typical use is probably
1001
'merge_inner(branch, branch.get_revision_tree(other_revision),
1002
branch.get_revision_tree(base_revision))'
1004
if this_tree is None:
1005
warnings.warn("bzrlib.merge.merge_inner requires a this_tree parameter as of "
1006
"bzrlib version 0.8.",
1009
this_tree = this_branch.bzrdir.open_workingtree()
1010
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1011
pb=pb, change_reporter=change_reporter)
1012
merger.backup_files = backup_files
1013
merger.merge_type = merge_type
1014
merger.interesting_ids = interesting_ids
1015
merger.ignore_zero = ignore_zero
1016
if interesting_files:
1017
assert not interesting_ids, ('Only supply interesting_ids'
1018
' or interesting_files')
1019
merger._set_interesting_files(interesting_files)
1020
merger.show_base = show_base
1021
merger.reprocess = reprocess
1022
merger.other_rev_id = other_rev_id
1023
merger.other_basis = other_rev_id
1024
return merger.do_merge()
1026
def get_merge_type_registry():
1027
"""Merge type registry is in bzrlib.option to avoid circular imports.
1029
This method provides a sanctioned way to retrieve it.
1031
from bzrlib import option
1032
return option._merge_type_registry