1
# Copyright (C) 2005 Canonical Ltd
1
# Copyright (C) 2005, 2006 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
20
from shutil import rmtree
21
20
from tempfile import mkdtemp
24
23
from bzrlib.branch import Branch
24
from bzrlib.conflicts import ConflictList, Conflict
25
25
from bzrlib.delta import compare_trees
26
26
from bzrlib.errors import (BzrCommandError,
35
36
WorkingTreeNotRevision,
37
39
from bzrlib.merge3 import Merge3
38
40
import bzrlib.osutils
39
from bzrlib.osutils import rename, pathjoin
41
from bzrlib.osutils import rename, pathjoin, rmtree
40
42
from progress import DummyProgress, ProgressPhase
41
43
from bzrlib.revision import common_ancestor, is_ancestor, NULL_REVISION
42
44
from bzrlib.symbol_versioning import *
45
from bzrlib.textfile import check_text_lines
43
46
from bzrlib.trace import mutter, warning, note
44
47
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
45
conflicts_strings, FinalPaths, create_by_entry,
48
FinalPaths, create_by_entry, unique_add)
49
from bzrlib.versionedfile import WeaveMerge
49
52
# TODO: Report back as changes are merged in
243
246
if self.merge_type.supports_reprocess:
244
247
kwargs['reprocess'] = self.reprocess
245
248
elif self.reprocess:
246
raise BzrError("Reprocess is not supported for this merge"
247
" type. %s" % merge_type)
249
raise BzrError("Conflict reduction is not supported for merge"
250
" type %s." % self.merge_type)
248
251
if self.merge_type.supports_show_base:
249
252
kwargs['show_base'] = self.show_base
250
253
elif self.show_base:
281
284
for file_id in old_entries:
282
285
entry = old_entries[file_id]
283
286
path = id2path(file_id)
287
if file_id in self.base_tree.inventory:
288
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
290
executable = getattr(entry, 'executable', False)
284
291
new_inventory[file_id] = (path, file_id, entry.parent_id,
292
entry.kind, executable)
286
294
by_path[path] = file_id
305
313
parent = by_path[os.path.dirname(path)]
306
314
abspath = pathjoin(self.this_tree.basedir, path)
307
315
kind = bzrlib.osutils.file_kind(abspath)
308
new_inventory[file_id] = (path, file_id, parent, kind)
316
if file_id in self.base_tree.inventory:
317
executable = getattr(self.base_tree.inventory[file_id], 'executable', False)
320
new_inventory[file_id] = (path, file_id, parent, kind, executable)
309
321
by_path[path] = file_id
311
323
# Get a list in insertion order
371
383
child_pb.finished()
372
384
self.cook_conflicts(fs_conflicts)
373
for line in conflicts_strings(self.cooked_conflicts):
385
for conflict in self.cooked_conflicts:
375
387
self.pp.next_phase()
376
388
results = self.tt.apply()
377
389
self.write_modified(results)
391
working_tree.set_conflicts(ConflictList(self.cooked_conflicts))
392
except UnsupportedOperation:
380
396
self.tt.finalize()
514
530
return kind, contents
532
def contents_conflict():
533
trans_id = self.tt.trans_id_file_id(file_id)
534
name = self.tt.final_name(trans_id)
535
parent_id = self.tt.final_parent(trans_id)
536
if file_id in self.this_tree.inventory:
537
self.tt.unversion_file(trans_id)
538
self.tt.delete_contents(trans_id)
539
file_group = self._dump_conflicts(name, parent_id, file_id,
541
self._raw_conflicts.append(('contents conflict', file_group))
515
543
# See SPOT run. run, SPOT, run.
516
544
# So we're not QUITE repeating ourselves; we do tricky things with
548
576
# THIS and OTHER are both files, so text merge. Either
549
577
# BASE is a file, or both converted to files, so at least we
550
578
# have agreement that output should be a file.
580
self.text_merge(file_id, trans_id)
582
return contents_conflict()
551
583
if file_id not in self.this_tree.inventory:
552
584
self.tt.version_file(file_id, trans_id)
553
self.text_merge(file_id, trans_id)
555
586
self.tt.tree_kind(trans_id)
556
587
self.tt.delete_contents(trans_id)
559
590
return "modified"
561
592
# Scalar conflict, can't text merge. Dump conflicts
562
trans_id = self.tt.trans_id_file_id(file_id)
563
name = self.tt.final_name(trans_id)
564
parent_id = self.tt.final_parent(trans_id)
565
if file_id in self.this_tree.inventory:
566
self.tt.unversion_file(trans_id)
567
self.tt.delete_contents(trans_id)
568
file_group = self._dump_conflicts(name, parent_id, file_id,
570
self._raw_conflicts.append(('contents conflict', file_group))
593
return contents_conflict()
572
595
def get_lines(self, tree, file_id):
573
596
"""Return the lines in a file, or an empty list."""
693
716
def cook_conflicts(self, fs_conflicts):
694
717
"""Convert all conflicts into a form that doesn't depend on trans_id"""
718
from conflicts import Conflict
695
719
name_conflicts = {}
696
720
self.cooked_conflicts.extend(cook_conflicts(fs_conflicts, self.tt))
697
721
fp = FinalPaths(self.tt)
714
738
if path.endswith(suffix):
715
739
path = path[:-len(suffix)]
717
self.cooked_conflicts.append((conflict_type, file_id, path))
741
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
742
self.cooked_conflicts.append(c)
718
743
if conflict_type == 'text conflict':
719
744
trans_id = conflict[1]
720
745
path = fp.get_path(trans_id)
721
746
file_id = self.tt.final_file_id(trans_id)
722
self.cooked_conflicts.append((conflict_type, file_id, path))
747
c = Conflict.factory(conflict_type, path=path, file_id=file_id)
748
self.cooked_conflicts.append(c)
724
750
for trans_id, conflicts in name_conflicts.iteritems():
742
768
this_path = "<deleted>"
743
769
file_id = self.tt.final_file_id(trans_id)
744
self.cooked_conflicts.append(('path conflict', file_id, this_path,
770
c = Conflict.factory('path conflict', path=this_path,
771
conflict_path=other_path, file_id=file_id)
772
self.cooked_conflicts.append(c)
773
self.cooked_conflicts.sort(key=Conflict.sort_key)
748
776
class WeaveMerger(Merge3Merger):
749
777
"""Three-way tree merger, text weave merger."""
750
supports_reprocess = False
778
supports_reprocess = True
751
779
supports_show_base = False
753
781
def __init__(self, working_tree, this_tree, base_tree, other_tree,
754
interesting_ids=None, pb=DummyProgress(), pp=None):
782
interesting_ids=None, pb=DummyProgress(), pp=None,
755
784
self.this_revision_tree = self._get_revision_tree(this_tree)
756
785
self.other_revision_tree = self._get_revision_tree(other_tree)
757
786
super(WeaveMerger, self).__init__(working_tree, this_tree,
758
787
base_tree, other_tree,
759
788
interesting_ids=interesting_ids,
789
pb=pb, pp=pp, reprocess=reprocess)
762
791
def _get_revision_tree(self, tree):
763
792
"""Return a revision tree releated to this tree.
787
816
this_revision_id = self.this_revision_tree.inventory[file_id].revision
788
817
other_revision_id = \
789
818
self.other_revision_tree.inventory[file_id].revision
790
plan = weave.plan_merge(this_revision_id, other_revision_id)
791
return weave.weave_merge(plan, '<<<<<<< TREE\n',
792
'>>>>>>> MERGE-SOURCE\n')
819
wm = WeaveMerge(weave, this_revision_id, other_revision_id,
820
'<<<<<<< TREE\n', '>>>>>>> MERGE-SOURCE\n')
821
return wm.merge_lines(self.reprocess)
794
823
def text_merge(self, file_id, trans_id):
795
824
"""Perform a (weave) text merge for a given file and file-id.
797
826
and a conflict will be noted.
799
828
self._check_file(file_id)
800
lines = list(self._merged_lines(file_id))
801
conflicts = '<<<<<<< TREE\n' in lines
829
lines, conflicts = self._merged_lines(file_id)
831
# Note we're checking whether the OUTPUT is binary in this case,
832
# because we don't want to get into weave merge guts.
833
check_text_lines(lines)
802
834
self.tt.create_file(lines, trans_id)
804
836
self._raw_conflicts.append(('text conflict', trans_id))