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
25
revision as _mod_revision,
27
from bzrlib.branch import Branch
28
from bzrlib.conflicts import ConflictList, Conflict
29
from bzrlib.errors import (BzrCommandError,
39
WorkingTreeNotRevision,
42
from bzrlib.merge3 import Merge3
43
from bzrlib.osutils import rename, pathjoin
44
from progress import DummyProgress, ProgressPhase
45
from bzrlib.revision import (is_ancestor, NULL_REVISION, ensure_null)
46
from bzrlib.textfile import check_text_lines
47
from bzrlib.trace import mutter, warning, note
48
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
49
FinalPaths, create_by_entry, unique_add,
51
from bzrlib.versionedfile import WeaveMerge
54
# TODO: Report back as changes are merged in
56
def _get_tree(treespec, local_branch=None):
57
from bzrlib import workingtree
58
location, revno = treespec
60
tree = workingtree.WorkingTree.open_containing(location)[0]
61
return tree.branch, tree
62
branch = Branch.open_containing(location)[0]
64
revision_id = branch.last_revision()
66
revision_id = branch.get_rev_id(revno)
67
if revision_id is None:
68
revision_id = NULL_REVISION
69
return branch, _get_revid_tree(branch, revision_id, local_branch)
72
def _get_revid_tree(branch, revision_id, local_branch):
73
if revision_id is None:
74
base_tree = branch.bzrdir.open_workingtree()
76
if local_branch is not None:
77
if local_branch.base != branch.base:
78
local_branch.fetch(branch, revision_id)
79
base_tree = local_branch.repository.revision_tree(revision_id)
81
base_tree = branch.repository.revision_tree(revision_id)
85
def _get_revid_tree_from_tree(tree, revision_id, local_branch):
86
if revision_id is None:
88
if local_branch is not None:
89
if local_branch.base != tree.branch.base:
90
local_branch.fetch(tree.branch, revision_id)
91
return local_branch.repository.revision_tree(revision_id)
92
return tree.branch.repository.revision_tree(revision_id)
95
def transform_tree(from_tree, to_tree, interesting_ids=None):
96
merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
97
interesting_ids=interesting_ids, this_tree=from_tree)
100
class Merger(object):
101
def __init__(self, this_branch, other_tree=None, base_tree=None,
102
this_tree=None, pb=DummyProgress(), change_reporter=None,
104
object.__init__(self)
105
assert this_tree is not None, "this_tree is required"
106
self.this_branch = this_branch
107
self.this_basis = _mod_revision.ensure_null(
108
this_branch.last_revision())
109
self.this_rev_id = None
110
self.this_tree = this_tree
111
self.this_revision_tree = None
112
self.this_basis_tree = None
113
self.other_tree = other_tree
114
self.other_branch = None
115
self.base_tree = base_tree
116
self.ignore_zero = False
117
self.backup_files = False
118
self.interesting_ids = None
119
self.show_base = False
120
self.reprocess = False
123
self.recurse = recurse
124
self.change_reporter = change_reporter
126
def revision_tree(self, revision_id):
127
return self.this_branch.repository.revision_tree(revision_id)
129
def ensure_revision_trees(self):
130
if self.this_revision_tree is None:
131
self.this_basis_tree = self.this_branch.repository.revision_tree(
133
if self.this_basis == self.this_rev_id:
134
self.this_revision_tree = self.this_basis_tree
136
if self.other_rev_id is None:
137
other_basis_tree = self.revision_tree(self.other_basis)
138
changes = other_basis_tree.changes_from(self.other_tree)
139
if changes.has_changed():
140
raise WorkingTreeNotRevision(self.this_tree)
141
other_rev_id = self.other_basis
142
self.other_tree = other_basis_tree
144
def file_revisions(self, file_id):
145
self.ensure_revision_trees()
146
def get_id(tree, file_id):
147
revision_id = tree.inventory[file_id].revision
148
assert revision_id is not None
150
if self.this_rev_id is None:
151
if self.this_basis_tree.get_file_sha1(file_id) != \
152
self.this_tree.get_file_sha1(file_id):
153
raise WorkingTreeNotRevision(self.this_tree)
155
trees = (self.this_basis_tree, self.other_tree)
156
return [get_id(tree, file_id) for tree in trees]
158
def check_basis(self, check_clean, require_commits=True):
159
if self.this_basis is None and require_commits is True:
160
raise BzrCommandError("This branch has no commits."
161
" (perhaps you would prefer 'bzr pull')")
164
if self.this_basis != self.this_rev_id:
165
raise BzrCommandError("Working tree has uncommitted changes.")
167
def compare_basis(self):
168
changes = self.this_tree.changes_from(self.this_tree.basis_tree())
169
if not changes.has_changed():
170
self.this_rev_id = self.this_basis
172
def set_interesting_files(self, file_list):
174
self._set_interesting_files(file_list)
175
except NotVersionedError, e:
176
raise BzrCommandError("%s is not a source file in any"
179
def _set_interesting_files(self, file_list):
180
"""Set the list of interesting ids from a list of files."""
181
if file_list is None:
182
self.interesting_ids = None
185
interesting_ids = set()
186
for path in file_list:
188
# TODO: jam 20070226 The trees are not locked at this time,
189
# wouldn't it make merge faster if it locks everything in the
190
# beginning? It locks at do_merge time, but this happens
192
for tree in (self.this_tree, self.base_tree, self.other_tree):
193
file_id = tree.path2id(path)
194
if file_id is not None:
195
interesting_ids.add(file_id)
198
raise NotVersionedError(path=path)
199
self.interesting_ids = interesting_ids
201
def set_pending(self):
202
if not self.base_is_ancestor:
204
if self.other_rev_id is None:
206
ancestry = set(self.this_branch.repository.get_ancestry(
207
self.this_basis, topo_sorted=False))
208
if self.other_rev_id in ancestry:
210
self.this_tree.add_parent_tree((self.other_rev_id, self.other_tree))
212
def set_other(self, other_revision):
213
"""Set the revision and tree to merge from.
215
This sets the other_tree, other_rev_id, other_basis attributes.
217
:param other_revision: The [path, revision] list to merge from.
219
self.other_branch, self.other_tree = _get_tree(other_revision,
221
if other_revision[1] == -1:
222
self.other_rev_id = _mod_revision.ensure_null(
223
self.other_branch.last_revision())
224
if _mod_revision.is_null(self.other_rev_id):
225
raise NoCommits(self.other_branch)
226
self.other_basis = self.other_rev_id
227
elif other_revision[1] is not None:
228
self.other_rev_id = self.other_branch.get_rev_id(other_revision[1])
229
self.other_basis = self.other_rev_id
231
self.other_rev_id = None
232
self.other_basis = self.other_branch.last_revision()
233
if self.other_basis is None:
234
raise NoCommits(self.other_branch)
235
if self.other_branch.base != self.this_branch.base:
236
self.this_branch.fetch(self.other_branch,
237
last_revision=self.other_basis)
239
def set_other_revision(self, revision_id, other_branch):
240
"""Set 'other' based on a branch and revision id
242
:param revision_id: The revision to use for a tree
243
:param other_branch: The branch containing this tree
245
self.other_rev_id = revision_id
246
self.other_branch = other_branch
247
self.this_branch.fetch(other_branch, self.other_rev_id)
248
self.other_tree = self.revision_tree(revision_id)
249
self.other_basis = revision_id
252
self.set_base([None, None])
254
def set_base(self, base_revision):
255
"""Set the base revision to use for the merge.
257
:param base_revision: A 2-list containing a path and revision number.
259
mutter("doing merge() with no base_revision specified")
260
if base_revision == [None, None]:
262
pb = ui.ui_factory.nested_progress_bar()
264
this_repo = self.this_branch.repository
265
graph = this_repo.get_graph()
266
revisions = [ensure_null(self.this_basis),
267
ensure_null(self.other_basis)]
268
if NULL_REVISION in revisions:
269
self.base_rev_id = NULL_REVISION
271
self.base_rev_id = graph.find_unique_lca(*revisions)
272
if self.base_rev_id == NULL_REVISION:
273
raise UnrelatedBranches()
276
except NoCommonAncestor:
277
raise UnrelatedBranches()
278
self.base_tree = _get_revid_tree_from_tree(self.this_tree,
281
self.base_is_ancestor = True
283
base_branch, self.base_tree = _get_tree(base_revision)
284
if base_revision[1] == -1:
285
self.base_rev_id = base_branch.last_revision()
286
elif base_revision[1] is None:
287
self.base_rev_id = _mod_revision.NULL_REVISION
289
self.base_rev_id = _mod_revision.ensure_null(
290
base_branch.get_rev_id(base_revision[1]))
291
if self.this_branch.base != base_branch.base:
292
self.this_branch.fetch(base_branch)
293
self.base_is_ancestor = is_ancestor(self.this_basis,
298
kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
299
'other_tree': self.other_tree,
300
'interesting_ids': self.interesting_ids,
302
if self.merge_type.requires_base:
303
kwargs['base_tree'] = self.base_tree
304
if self.merge_type.supports_reprocess:
305
kwargs['reprocess'] = self.reprocess
307
raise BzrError("Conflict reduction is not supported for merge"
308
" type %s." % self.merge_type)
309
if self.merge_type.supports_show_base:
310
kwargs['show_base'] = self.show_base
312
raise BzrError("Showing base is not supported for this"
313
" merge type. %s" % self.merge_type)
314
self.this_tree.lock_tree_write()
315
if self.base_tree is not None:
316
self.base_tree.lock_read()
317
if self.other_tree is not None:
318
self.other_tree.lock_read()
320
merge = self.merge_type(pb=self._pb,
321
change_reporter=self.change_reporter,
323
if self.recurse == 'down':
324
for path, file_id in self.this_tree.iter_references():
325
sub_tree = self.this_tree.get_nested_tree(file_id, path)
326
other_revision = self.other_tree.get_reference_revision(
328
if other_revision == sub_tree.last_revision():
330
sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
331
sub_merge.merge_type = self.merge_type
332
relpath = self.this_tree.relpath(path)
333
other_branch = self.other_branch.reference_parent(file_id, relpath)
334
sub_merge.set_other_revision(other_revision, other_branch)
335
base_revision = self.base_tree.get_reference_revision(file_id)
336
sub_merge.base_tree = \
337
sub_tree.branch.repository.revision_tree(base_revision)
341
if self.other_tree is not None:
342
self.other_tree.unlock()
343
if self.base_tree is not None:
344
self.base_tree.unlock()
345
self.this_tree.unlock()
346
if len(merge.cooked_conflicts) == 0:
347
if not self.ignore_zero:
348
note("All changes applied successfully.")
350
note("%d conflicts encountered." % len(merge.cooked_conflicts))
352
return len(merge.cooked_conflicts)
354
def regen_inventory(self, new_entries):
355
old_entries = self.this_tree.read_working_inventory()
359
for path, file_id in new_entries:
362
new_entries_map[file_id] = path
364
def id2path(file_id):
365
path = new_entries_map.get(file_id)
368
entry = old_entries[file_id]
369
if entry.parent_id is None:
371
return pathjoin(id2path(entry.parent_id), entry.name)
373
for file_id in old_entries:
374
entry = old_entries[file_id]
375
path = id2path(file_id)
376
if file_id in self.base_tree.inventory:
377
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
379
executable = getattr(entry, 'executable', False)
380
new_inventory[file_id] = (path, file_id, entry.parent_id,
381
entry.kind, executable)
383
by_path[path] = file_id
388
for path, file_id in new_entries:
390
del new_inventory[file_id]
393
new_path_list.append((path, file_id))
394
if file_id not in old_entries:
396
# Ensure no file is added before its parent
398
for path, file_id in new_path_list:
402
parent = by_path[os.path.dirname(path)]
403
abspath = pathjoin(self.this_tree.basedir, path)
404
kind = osutils.file_kind(abspath)
405
if file_id in self.base_tree.inventory:
406
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
409
new_inventory[file_id] = (path, file_id, parent, kind, executable)
410
by_path[path] = file_id
412
# Get a list in insertion order
413
new_inventory_list = new_inventory.values()
414
mutter ("""Inventory regeneration:
415
old length: %i insertions: %i deletions: %i new_length: %i"""\
416
% (len(old_entries), insertions, deletions,
417
len(new_inventory_list)))
418
assert len(new_inventory_list) == len(old_entries) + insertions\
420
new_inventory_list.sort()
421
return new_inventory_list
424
class Merge3Merger(object):
425
"""Three-way merger that uses the merge3 text merger"""
427
supports_reprocess = True
428
supports_show_base = True
429
history_based = False
431
def __init__(self, working_tree, this_tree, base_tree, other_tree,
432
interesting_ids=None, reprocess=False, show_base=False,
433
pb=DummyProgress(), pp=None, change_reporter=None):
434
"""Initialize the merger object and perform the merge."""
435
object.__init__(self)
436
self.this_tree = working_tree
437
self.this_tree.lock_tree_write()
438
self.base_tree = base_tree
439
self.base_tree.lock_read()
440
self.other_tree = other_tree
441
self.other_tree.lock_read()
442
self._raw_conflicts = []
443
self.cooked_conflicts = []
444
self.reprocess = reprocess
445
self.show_base = show_base
448
self.change_reporter = change_reporter
450
self.pp = ProgressPhase("Merge phase", 3, self.pb)
452
if interesting_ids is not None:
453
all_ids = interesting_ids
455
all_ids = set(base_tree)
456
all_ids.update(other_tree)
457
self.tt = TreeTransform(working_tree, self.pb)
460
child_pb = ui.ui_factory.nested_progress_bar()
462
for num, file_id in enumerate(all_ids):
463
child_pb.update('Preparing file merge', num, len(all_ids))
464
self.merge_names(file_id)
465
file_status = self.merge_contents(file_id)
466
self.merge_executable(file_id, file_status)
471
child_pb = ui.ui_factory.nested_progress_bar()
473
fs_conflicts = resolve_conflicts(self.tt, child_pb)
476
if change_reporter is not None:
477
from bzrlib import delta
478
delta.report_changes(self.tt._iter_changes(), change_reporter)
479
self.cook_conflicts(fs_conflicts)
480
for conflict in self.cooked_conflicts:
483
results = self.tt.apply()
484
self.write_modified(results)
486
working_tree.add_conflicts(self.cooked_conflicts)
487
except UnsupportedOperation:
491
self.other_tree.unlock()
492
self.base_tree.unlock()
493
self.this_tree.unlock()
498
self.tt.final_kind(self.tt.root)
500
self.tt.cancel_deletion(self.tt.root)
501
if self.tt.final_file_id(self.tt.root) is None:
502
self.tt.version_file(self.tt.tree_file_id(self.tt.root),
504
if self.other_tree.inventory.root is None:
506
other_root_file_id = self.other_tree.inventory.root.file_id
507
other_root = self.tt.trans_id_file_id(other_root_file_id)
508
if other_root == self.tt.root:
511
self.tt.final_kind(other_root)
514
self.reparent_children(self.other_tree.inventory.root, self.tt.root)
515
self.tt.cancel_creation(other_root)
516
self.tt.cancel_versioning(other_root)
518
def reparent_children(self, ie, target):
519
for thing, child in ie.children.iteritems():
520
trans_id = self.tt.trans_id_file_id(child.file_id)
521
self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
523
def write_modified(self, results):
525
for path in results.modified_paths:
526
file_id = self.this_tree.path2id(self.this_tree.relpath(path))
529
hash = self.this_tree.get_file_sha1(file_id)
532
modified_hashes[file_id] = hash
533
self.this_tree.set_merge_modified(modified_hashes)
536
def parent(entry, file_id):
537
"""Determine the parent for a file_id (used as a key method)"""
540
return entry.parent_id
543
def name(entry, file_id):
544
"""Determine the name for a file_id (used as a key method)"""
550
def contents_sha1(tree, file_id):
551
"""Determine the sha1 of the file contents (used as a key method)."""
552
if file_id not in tree:
554
return tree.get_file_sha1(file_id)
557
def executable(tree, file_id):
558
"""Determine the executability of a file-id (used as a key method)."""
559
if file_id not in tree:
561
if tree.kind(file_id) != "file":
563
return tree.is_executable(file_id)
566
def kind(tree, file_id):
567
"""Determine the kind of a file-id (used as a key method)."""
568
if file_id not in tree:
570
return tree.kind(file_id)
573
def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
574
"""Do a three-way test on a scalar.
575
Return "this", "other" or "conflict", depending whether a value wins.
577
key_base = key(base_tree, file_id)
578
key_other = key(other_tree, file_id)
579
#if base == other, either they all agree, or only THIS has changed.
580
if key_base == key_other:
582
key_this = key(this_tree, file_id)
583
if key_this not in (key_base, key_other):
585
# "Ambiguous clean merge"
586
elif key_this == key_other:
589
assert key_this == key_base
592
def merge_names(self, file_id):
593
"""Perform a merge on file_id names and parents"""
595
if file_id in tree.inventory:
596
return tree.inventory[file_id]
599
this_entry = get_entry(self.this_tree)
600
other_entry = get_entry(self.other_tree)
601
base_entry = get_entry(self.base_tree)
602
name_winner = self.scalar_three_way(this_entry, base_entry,
603
other_entry, file_id, self.name)
604
parent_id_winner = self.scalar_three_way(this_entry, base_entry,
605
other_entry, file_id,
607
if this_entry is None:
608
if name_winner == "this":
609
name_winner = "other"
610
if parent_id_winner == "this":
611
parent_id_winner = "other"
612
if name_winner == "this" and parent_id_winner == "this":
614
if name_winner == "conflict":
615
trans_id = self.tt.trans_id_file_id(file_id)
616
self._raw_conflicts.append(('name conflict', trans_id,
617
self.name(this_entry, file_id),
618
self.name(other_entry, file_id)))
619
if parent_id_winner == "conflict":
620
trans_id = self.tt.trans_id_file_id(file_id)
621
self._raw_conflicts.append(('parent conflict', trans_id,
622
self.parent(this_entry, file_id),
623
self.parent(other_entry, file_id)))
624
if other_entry is None:
625
# it doesn't matter whether the result was 'other' or
626
# 'conflict'-- if there's no 'other', we leave it alone.
628
# if we get here, name_winner and parent_winner are set to safe values.
629
winner_entry = {"this": this_entry, "other": other_entry,
630
"conflict": other_entry}
631
trans_id = self.tt.trans_id_file_id(file_id)
632
parent_id = winner_entry[parent_id_winner].parent_id
633
if parent_id is not None:
634
parent_trans_id = self.tt.trans_id_file_id(parent_id)
635
self.tt.adjust_path(winner_entry[name_winner].name,
636
parent_trans_id, trans_id)
638
def merge_contents(self, file_id):
639
"""Performa a merge on file_id contents."""
640
def contents_pair(tree):
641
if file_id not in tree:
643
kind = tree.kind(file_id)
645
contents = tree.get_file_sha1(file_id)
646
elif kind == "symlink":
647
contents = tree.get_symlink_target(file_id)
650
return kind, contents
652
def contents_conflict():
653
trans_id = self.tt.trans_id_file_id(file_id)
654
name = self.tt.final_name(trans_id)
655
parent_id = self.tt.final_parent(trans_id)
656
if file_id in self.this_tree.inventory:
657
self.tt.unversion_file(trans_id)
658
if file_id in self.this_tree:
659
self.tt.delete_contents(trans_id)
660
file_group = self._dump_conflicts(name, parent_id, file_id,
662
self._raw_conflicts.append(('contents conflict', file_group))
664
# See SPOT run. run, SPOT, run.
665
# So we're not QUITE repeating ourselves; we do tricky things with
667
base_pair = contents_pair(self.base_tree)
668
other_pair = contents_pair(self.other_tree)
669
if base_pair == other_pair:
670
# OTHER introduced no changes
672
this_pair = contents_pair(self.this_tree)
673
if this_pair == other_pair:
674
# THIS and OTHER introduced the same changes
677
trans_id = self.tt.trans_id_file_id(file_id)
678
if this_pair == base_pair:
679
# only OTHER introduced changes
680
if file_id in self.this_tree:
681
# Remove any existing contents
682
self.tt.delete_contents(trans_id)
683
if file_id in self.other_tree:
684
# OTHER changed the file
685
create_by_entry(self.tt,
686
self.other_tree.inventory[file_id],
687
self.other_tree, trans_id)
688
if file_id not in self.this_tree.inventory:
689
self.tt.version_file(file_id, trans_id)
691
elif file_id in self.this_tree.inventory:
692
# OTHER deleted the file
693
self.tt.unversion_file(trans_id)
695
#BOTH THIS and OTHER introduced changes; scalar conflict
696
elif this_pair[0] == "file" and other_pair[0] == "file":
697
# THIS and OTHER are both files, so text merge. Either
698
# BASE is a file, or both converted to files, so at least we
699
# have agreement that output should be a file.
701
self.text_merge(file_id, trans_id)
703
return contents_conflict()
704
if file_id not in self.this_tree.inventory:
705
self.tt.version_file(file_id, trans_id)
707
self.tt.tree_kind(trans_id)
708
self.tt.delete_contents(trans_id)
713
# Scalar conflict, can't text merge. Dump conflicts
714
return contents_conflict()
716
def get_lines(self, tree, file_id):
717
"""Return the lines in a file, or an empty list."""
719
return tree.get_file(file_id).readlines()
723
def text_merge(self, file_id, trans_id):
724
"""Perform a three-way text merge on a file_id"""
725
# it's possible that we got here with base as a different type.
726
# if so, we just want two-way text conflicts.
727
if file_id in self.base_tree and \
728
self.base_tree.kind(file_id) == "file":
729
base_lines = self.get_lines(self.base_tree, file_id)
732
other_lines = self.get_lines(self.other_tree, file_id)
733
this_lines = self.get_lines(self.this_tree, file_id)
734
m3 = Merge3(base_lines, this_lines, other_lines)
735
start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
736
if self.show_base is True:
737
base_marker = '|' * 7
741
def iter_merge3(retval):
742
retval["text_conflicts"] = False
743
for line in m3.merge_lines(name_a = "TREE",
744
name_b = "MERGE-SOURCE",
745
name_base = "BASE-REVISION",
746
start_marker=start_marker,
747
base_marker=base_marker,
748
reprocess=self.reprocess):
749
if line.startswith(start_marker):
750
retval["text_conflicts"] = True
751
yield line.replace(start_marker, '<' * 7)
755
merge3_iterator = iter_merge3(retval)
756
self.tt.create_file(merge3_iterator, trans_id)
757
if retval["text_conflicts"] is True:
758
self._raw_conflicts.append(('text conflict', trans_id))
759
name = self.tt.final_name(trans_id)
760
parent_id = self.tt.final_parent(trans_id)
761
file_group = self._dump_conflicts(name, parent_id, file_id,
762
this_lines, base_lines,
764
file_group.append(trans_id)
766
def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
767
base_lines=None, other_lines=None, set_version=False,
769
"""Emit conflict files.
770
If this_lines, base_lines, or other_lines are omitted, they will be
771
determined automatically. If set_version is true, the .OTHER, .THIS
772
or .BASE (in that order) will be created as versioned files.
774
data = [('OTHER', self.other_tree, other_lines),
775
('THIS', self.this_tree, this_lines)]
777
data.append(('BASE', self.base_tree, base_lines))
780
for suffix, tree, lines in data:
782
trans_id = self._conflict_file(name, parent_id, tree, file_id,
784
file_group.append(trans_id)
785
if set_version and not versioned:
786
self.tt.version_file(file_id, trans_id)
790
def _conflict_file(self, name, parent_id, tree, file_id, suffix,
792
"""Emit a single conflict file."""
793
name = name + '.' + suffix
794
trans_id = self.tt.create_path(name, parent_id)
795
entry = tree.inventory[file_id]
796
create_by_entry(self.tt, entry, tree, trans_id, lines)
799
def merge_executable(self, file_id, file_status):
800
"""Perform a merge on the execute bit."""
801
if file_status == "deleted":
803
trans_id = self.tt.trans_id_file_id(file_id)
805
if self.tt.final_kind(trans_id) != "file":
809
winner = self.scalar_three_way(self.this_tree, self.base_tree,
810
self.other_tree, file_id,
812
if winner == "conflict":
813
# There must be a None in here, if we have a conflict, but we
814
# need executability since file status was not deleted.
815
if self.executable(self.other_tree, file_id) is None:
820
if file_status == "modified":
821
executability = self.this_tree.is_executable(file_id)
822
if executability is not None:
823
trans_id = self.tt.trans_id_file_id(file_id)
824
self.tt.set_executability(executability, trans_id)
826
assert winner == "other"
827
if file_id in self.other_tree:
828
executability = self.other_tree.is_executable(file_id)
829
elif file_id in self.this_tree:
830
executability = self.this_tree.is_executable(file_id)
831
elif file_id in self.base_tree:
832
executability = self.base_tree.is_executable(file_id)
833
if executability is not None:
834
trans_id = self.tt.trans_id_file_id(file_id)
835
self.tt.set_executability(executability, trans_id)
837
def cook_conflicts(self, fs_conflicts):
838
"""Convert all conflicts into a form that doesn't depend on trans_id"""
839
from conflicts import Conflict
841
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
842
fp = FinalPaths(self.tt)
843
for conflict in self._raw_conflicts:
844
conflict_type = conflict[0]
845
if conflict_type in ('name conflict', 'parent conflict'):
846
trans_id = conflict[1]
847
conflict_args = conflict[2:]
848
if trans_id not in name_conflicts:
849
name_conflicts[trans_id] = {}
850
unique_add(name_conflicts[trans_id], conflict_type,
852
if conflict_type == 'contents conflict':
853
for trans_id in conflict[1]:
854
file_id = self.tt.final_file_id(trans_id)
855
if file_id is not None:
857
path = fp.get_path(trans_id)
858
for suffix in ('.BASE', '.THIS', '.OTHER'):
859
if path.endswith(suffix):
860
path = path[:-len(suffix)]
862
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
863
self.cooked_conflicts.append(c)
864
if conflict_type == 'text conflict':
865
trans_id = conflict[1]
866
path = fp.get_path(trans_id)
867
file_id = self.tt.final_file_id(trans_id)
868
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
869
self.cooked_conflicts.append(c)
871
for trans_id, conflicts in name_conflicts.iteritems():
873
this_parent, other_parent = conflicts['parent conflict']
874
assert this_parent != other_parent
876
this_parent = other_parent = \
877
self.tt.final_file_id(self.tt.final_parent(trans_id))
879
this_name, other_name = conflicts['name conflict']
880
assert this_name != other_name
882
this_name = other_name = self.tt.final_name(trans_id)
883
other_path = fp.get_path(trans_id)
884
if this_parent is not None and this_name is not None:
886
fp.get_path(self.tt.trans_id_file_id(this_parent))
887
this_path = pathjoin(this_parent_path, this_name)
889
this_path = "<deleted>"
890
file_id = self.tt.final_file_id(trans_id)
891
c = Conflict.factory('path conflict', path=this_path,
892
conflict_path=other_path, file_id=file_id)
893
self.cooked_conflicts.append(c)
894
self.cooked_conflicts.sort(key=Conflict.sort_key)
897
class WeaveMerger(Merge3Merger):
898
"""Three-way tree merger, text weave merger."""
899
supports_reprocess = True
900
supports_show_base = False
902
def __init__(self, working_tree, this_tree, base_tree, other_tree,
903
interesting_ids=None, pb=DummyProgress(), pp=None,
904
reprocess=False, change_reporter=None):
905
self.this_revision_tree = self._get_revision_tree(this_tree)
906
self.other_revision_tree = self._get_revision_tree(other_tree)
907
super(WeaveMerger, self).__init__(working_tree, this_tree,
908
base_tree, other_tree,
909
interesting_ids=interesting_ids,
910
pb=pb, pp=pp, reprocess=reprocess,
911
change_reporter=change_reporter)
913
def _get_revision_tree(self, tree):
914
"""Return a revision tree related to this tree.
915
If the tree is a WorkingTree, the basis will be returned.
917
if getattr(tree, 'get_weave', False) is False:
918
# If we have a WorkingTree, try using the basis
919
return tree.branch.basis_tree()
923
def _check_file(self, file_id):
924
"""Check that the revision tree's version of the file matches."""
925
for tree, rt in ((self.this_tree, self.this_revision_tree),
926
(self.other_tree, self.other_revision_tree)):
929
if tree.get_file_sha1(file_id) != rt.get_file_sha1(file_id):
930
raise WorkingTreeNotRevision(self.this_tree)
932
def _merged_lines(self, file_id):
933
"""Generate the merged lines.
934
There is no distinction between lines that are meant to contain <<<<<<<
937
weave = self.this_revision_tree.get_weave(file_id)
938
this_revision_id = self.this_revision_tree.inventory[file_id].revision
939
other_revision_id = \
940
self.other_revision_tree.inventory[file_id].revision
941
wm = WeaveMerge(weave, this_revision_id, other_revision_id,
942
'<<<<<<< TREE\n', '>>>>>>> MERGE-SOURCE\n')
943
return wm.merge_lines(self.reprocess)
945
def text_merge(self, file_id, trans_id):
946
"""Perform a (weave) text merge for a given file and file-id.
947
If conflicts are encountered, .THIS and .OTHER files will be emitted,
948
and a conflict will be noted.
950
self._check_file(file_id)
951
lines, conflicts = self._merged_lines(file_id)
953
# Note we're checking whether the OUTPUT is binary in this case,
954
# because we don't want to get into weave merge guts.
955
check_text_lines(lines)
956
self.tt.create_file(lines, trans_id)
958
self._raw_conflicts.append(('text conflict', trans_id))
959
name = self.tt.final_name(trans_id)
960
parent_id = self.tt.final_parent(trans_id)
961
file_group = self._dump_conflicts(name, parent_id, file_id,
963
file_group.append(trans_id)
966
class Diff3Merger(Merge3Merger):
967
"""Three-way merger using external diff3 for text merging"""
969
def dump_file(self, temp_dir, name, tree, file_id):
970
out_path = pathjoin(temp_dir, name)
971
out_file = open(out_path, "wb")
973
in_file = tree.get_file(file_id)
980
def text_merge(self, file_id, trans_id):
981
"""Perform a diff3 merge using a specified file-id and trans-id.
982
If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
983
will be dumped, and a will be conflict noted.
986
temp_dir = osutils.mkdtemp(prefix="bzr-")
988
new_file = pathjoin(temp_dir, "new")
989
this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
990
base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
991
other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
992
status = bzrlib.patch.diff3(new_file, this, base, other)
993
if status not in (0, 1):
994
raise BzrError("Unhandled diff3 exit code")
995
f = open(new_file, 'rb')
997
self.tt.create_file(f, trans_id)
1001
name = self.tt.final_name(trans_id)
1002
parent_id = self.tt.final_parent(trans_id)
1003
self._dump_conflicts(name, parent_id, file_id)
1004
self._raw_conflicts.append(('text conflict', trans_id))
1006
osutils.rmtree(temp_dir)
1009
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1011
merge_type=Merge3Merger,
1012
interesting_ids=None,
1016
interesting_files=None,
1019
change_reporter=None):
1020
"""Primary interface for merging.
1022
typical use is probably
1023
'merge_inner(branch, branch.get_revision_tree(other_revision),
1024
branch.get_revision_tree(base_revision))'
1026
if this_tree is None:
1027
raise BzrError("bzrlib.merge.merge_inner requires a this_tree "
1028
"parameter as of bzrlib version 0.8.")
1029
merger = Merger(this_branch, other_tree, base_tree, this_tree=this_tree,
1030
pb=pb, change_reporter=change_reporter)
1031
merger.backup_files = backup_files
1032
merger.merge_type = merge_type
1033
merger.interesting_ids = interesting_ids
1034
merger.ignore_zero = ignore_zero
1035
if interesting_files:
1036
assert not interesting_ids, ('Only supply interesting_ids'
1037
' or interesting_files')
1038
merger._set_interesting_files(interesting_files)
1039
merger.show_base = show_base
1040
merger.reprocess = reprocess
1041
merger.other_rev_id = other_rev_id
1042
merger.other_basis = other_rev_id
1043
return merger.do_merge()
1045
def get_merge_type_registry():
1046
"""Merge type registry is in bzrlib.option to avoid circular imports.
1048
This method provides a sanctioned way to retrieve it.
1050
from bzrlib import option
1051
return option._merge_type_registry