24
24
from bzrlib.branch import Branch
25
from bzrlib.conflicts import ConflictList, Conflict
25
26
from bzrlib.delta import compare_trees
26
27
from bzrlib.errors import (BzrCommandError,
40
43
from progress import DummyProgress, ProgressPhase
41
44
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
42
45
from bzrlib.symbol_versioning import *
46
from bzrlib.textfile import check_text_lines
43
47
from bzrlib.trace import mutter, warning, note
44
48
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
45
conflicts_strings, FinalPaths, create_by_entry,
49
FinalPaths, create_by_entry, unique_add)
50
from bzrlib.versionedfile import WeaveMerge
49
53
# TODO: Report back as changes are merged in
227
231
self.base_rev_id = None
229
233
self.base_rev_id = base_branch.get_rev_id(base_revision[1])
230
self.this_branch.fetch(base_branch)
234
if self.this_branch.base != base_branch.base:
235
self.this_branch.fetch(base_branch)
231
236
self.base_is_ancestor = is_ancestor(self.this_basis,
232
237
self.base_rev_id,
233
238
self.this_branch)
242
247
if self.merge_type.supports_reprocess:
243
248
kwargs['reprocess'] = self.reprocess
244
249
elif self.reprocess:
245
raise BzrError("Reprocess is not supported for this merge"
246
" type. %s" % merge_type)
250
raise BzrError("Conflict reduction is not supported for merge"
251
" type %s." % self.merge_type)
247
252
if self.merge_type.supports_show_base:
248
253
kwargs['show_base'] = self.show_base
249
254
elif self.show_base:
280
285
for file_id in old_entries:
281
286
entry = old_entries[file_id]
282
287
path = id2path(file_id)
288
if file_id in self.base_tree.inventory:
289
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
291
executable = getattr(entry, 'executable', False)
283
292
new_inventory[file_id] = (path, file_id, entry.parent_id,
293
entry.kind, executable)
285
295
by_path[path] = file_id
304
314
parent = by_path[os.path.dirname(path)]
305
315
abspath = pathjoin(self.this_tree.basedir, path)
306
316
kind = bzrlib.osutils.file_kind(abspath)
307
new_inventory[file_id] = (path, file_id, parent, kind)
317
if file_id in self.base_tree.inventory:
318
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
321
new_inventory[file_id] = (path, file_id, parent, kind, executable)
308
322
by_path[path] = file_id
310
324
# Get a list in insertion order
370
384
child_pb.finished()
371
385
self.cook_conflicts(fs_conflicts)
372
for line in conflicts_strings(self.cooked_conflicts):
386
for conflict in self.cooked_conflicts:
374
388
self.pp.next_phase()
375
389
results = self.tt.apply()
376
390
self.write_modified(results)
392
working_tree.set_conflicts(ConflictList(self.cooked_conflicts))
393
except UnsupportedOperation:
379
397
self.tt.finalize()
513
531
return kind, contents
533
def contents_conflict():
534
trans_id = self.tt.trans_id_file_id(file_id)
535
name = self.tt.final_name(trans_id)
536
parent_id = self.tt.final_parent(trans_id)
537
if file_id in self.this_tree.inventory:
538
self.tt.unversion_file(trans_id)
539
self.tt.delete_contents(trans_id)
540
file_group = self._dump_conflicts(name, parent_id, file_id,
542
self._raw_conflicts.append(('contents conflict', file_group))
514
544
# See SPOT run. run, SPOT, run.
515
545
# So we're not QUITE repeating ourselves; we do tricky things with
547
577
# THIS and OTHER are both files, so text merge. Either
548
578
# BASE is a file, or both converted to files, so at least we
549
579
# have agreement that output should be a file.
581
self.text_merge(file_id, trans_id)
583
return contents_conflict()
550
584
if file_id not in self.this_tree.inventory:
551
585
self.tt.version_file(file_id, trans_id)
552
self.text_merge(file_id, trans_id)
554
587
self.tt.tree_kind(trans_id)
555
588
self.tt.delete_contents(trans_id)
558
591
return "modified"
560
593
# Scalar conflict, can't text merge. Dump conflicts
561
trans_id = self.tt.trans_id_file_id(file_id)
562
name = self.tt.final_name(trans_id)
563
parent_id = self.tt.final_parent(trans_id)
564
if file_id in self.this_tree.inventory:
565
self.tt.unversion_file(trans_id)
566
self.tt.delete_contents(trans_id)
567
file_group = self._dump_conflicts(name, parent_id, file_id,
569
self._raw_conflicts.append(('contents conflict', file_group))
594
return contents_conflict()
571
596
def get_lines(self, tree, file_id):
572
597
"""Return the lines in a file, or an empty list."""
692
717
def cook_conflicts(self, fs_conflicts):
693
718
"""Convert all conflicts into a form that doesn't depend on trans_id"""
719
from conflicts import Conflict
694
720
name_conflicts = {}
695
721
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
696
722
fp = FinalPaths(self.tt)
713
739
if path.endswith(suffix):
714
740
path = path[:-len(suffix)]
716
self.cooked_conflicts.append((conflict_type, file_id, path))
742
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
743
self.cooked_conflicts.append(c)
717
744
if conflict_type == 'text conflict':
718
745
trans_id = conflict[1]
719
746
path = fp.get_path(trans_id)
720
747
file_id = self.tt.final_file_id(trans_id)
721
self.cooked_conflicts.append((conflict_type, file_id, path))
748
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
749
self.cooked_conflicts.append(c)
723
751
for trans_id, conflicts in name_conflicts.iteritems():
741
769
this_path = "<deleted>"
742
770
file_id = self.tt.final_file_id(trans_id)
743
self.cooked_conflicts.append(('path conflict', file_id, this_path,
771
c = Conflict.factory('path conflict', path=this_path,
772
conflict_path=other_path, file_id=file_id)
773
self.cooked_conflicts.append(c)
774
self.cooked_conflicts.sort(key=Conflict.sort_key)
747
777
class WeaveMerger(Merge3Merger):
748
778
"""Three-way tree merger, text weave merger."""
749
supports_reprocess = False
779
supports_reprocess = True
750
780
supports_show_base = False
752
782
def __init__(self, working_tree, this_tree, base_tree, other_tree,
753
interesting_ids=None, pb=DummyProgress(), pp=None):
783
interesting_ids=None, pb=DummyProgress(), pp=None,
754
785
self.this_revision_tree = self._get_revision_tree(this_tree)
755
786
self.other_revision_tree = self._get_revision_tree(other_tree)
756
787
super(WeaveMerger, self).__init__(working_tree, this_tree,
757
788
base_tree, other_tree,
758
789
interesting_ids=interesting_ids,
790
pb=pb, pp=pp, reprocess=reprocess)
761
792
def _get_revision_tree(self, tree):
762
793
"""Return a revision tree releated to this tree.
786
817
this_revision_id = self.this_revision_tree.inventory[file_id].revision
787
818
other_revision_id = \
788
819
self.other_revision_tree.inventory[file_id].revision
789
plan = weave.plan_merge(this_revision_id, other_revision_id)
790
return weave.weave_merge(plan, '<<<<<<< TREE\n',
791
'>>>>>>> MERGE-SOURCE\n')
820
wm = WeaveMerge(weave, this_revision_id, other_revision_id,
821
'<<<<<<< TREE\n', '>>>>>>> MERGE-SOURCE\n')
822
return wm.merge_lines(self.reprocess)
793
824
def text_merge(self, file_id, trans_id):
794
825
"""Perform a (weave) text merge for a given file and file-id.
796
827
and a conflict will be noted.
798
829
self._check_file(file_id)
799
lines = self._merged_lines(file_id)
800
conflicts = '<<<<<<< TREE\n' in lines
830
lines, conflicts = self._merged_lines(file_id)
832
# Note we're checking whether the OUTPUT is binary in this case,
833
# because we don't want to get into weave merge guts.
834
check_text_lines(lines)
801
835
self.tt.create_file(lines, trans_id)
803
837
self._raw_conflicts.append(('text conflict', trans_id))