~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: abentley
  • Date: 2006-04-20 23:47:53 UTC
  • mfrom: (1681 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1683.
  • Revision ID: abentley@lappy-20060420234753-6a6874b76f09f86d
Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
 
23
23
import bzrlib
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,
27
28
                           BzrError,
32
33
                           NotBranchError,
33
34
                           NotVersionedError,
34
35
                           UnrelatedBranches,
 
36
                           UnsupportedOperation,
35
37
                           WorkingTreeNotRevision,
 
38
                           BinaryFile,
36
39
                           )
37
40
from bzrlib.merge3 import Merge3
38
41
import bzrlib.osutils
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,
46
 
                              unique_add)
 
49
                              FinalPaths, create_by_entry, unique_add)
 
50
from bzrlib.versionedfile import WeaveMerge
47
51
import bzrlib.ui
48
52
 
49
53
# TODO: Report back as changes are merged in
227
231
                self.base_rev_id = None
228
232
            else:
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)
 
290
            else:
 
291
                executable = getattr(entry, 'executable', False)
283
292
            new_inventory[file_id] = (path, file_id, entry.parent_id, 
284
 
                                      entry.kind)
 
293
                                      entry.kind, executable)
 
294
                                      
285
295
            by_path[path] = file_id
286
296
        
287
297
        deletions = 0
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)
 
319
            else:
 
320
                executable = False
 
321
            new_inventory[file_id] = (path, file_id, parent, kind, executable)
308
322
            by_path[path] = file_id 
309
323
 
310
324
        # Get a list in insertion order
369
383
            finally:
370
384
                child_pb.finished()
371
385
            self.cook_conflicts(fs_conflicts)
372
 
            for line in conflicts_strings(self.cooked_conflicts):
373
 
                warning(line)
 
386
            for conflict in self.cooked_conflicts:
 
387
                warning(conflict)
374
388
            self.pp.next_phase()
375
389
            results = self.tt.apply()
376
390
            self.write_modified(results)
 
391
            try:
 
392
                working_tree.set_conflicts(ConflictList(self.cooked_conflicts))
 
393
            except UnsupportedOperation:
 
394
                pass
377
395
        finally:
378
396
            try:
379
397
                self.tt.finalize()
511
529
            else:
512
530
                contents = None
513
531
            return kind, contents
 
532
 
 
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, 
 
541
                                              set_version=True)
 
542
            self._raw_conflicts.append(('contents conflict', file_group))
 
543
 
514
544
        # See SPOT run.  run, SPOT, run.
515
545
        # So we're not QUITE repeating ourselves; we do tricky things with
516
546
        # file kind...
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.
 
580
                try:
 
581
                    self.text_merge(file_id, trans_id)
 
582
                except BinaryFile:
 
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)
553
586
                try:
554
587
                    self.tt.tree_kind(trans_id)
555
588
                    self.tt.delete_contents(trans_id)
558
591
                return "modified"
559
592
            else:
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, 
568
 
                                                  set_version=True)
569
 
                self._raw_conflicts.append(('contents conflict', file_group))
 
594
                return contents_conflict()
570
595
 
571
596
    def get_lines(self, tree, file_id):
572
597
        """Return the lines in a file, or an empty list."""
691
716
 
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)]
715
741
                        break
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)
722
750
 
723
751
        for trans_id, conflicts in name_conflicts.iteritems():
724
752
            try:
740
768
            else:
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, 
744
 
                                         other_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)
745
775
 
746
776
 
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
751
781
 
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,
 
784
                 reprocess=False):
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, 
759
 
                                          pb=pb, pp=pp)
 
790
                                          pb=pb, pp=pp, reprocess=reprocess)
760
791
 
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)
792
823
 
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.
797
828
        """
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)
 
831
        lines = list(lines)
 
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)
802
836
        if conflicts:
803
837
            self._raw_conflicts.append(('text conflict', trans_id))