~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-07-12 02:12:35 UTC
  • mfrom: (2590.2.10 changes-merge)
  • Revision ID: pqm@pqm.ubuntu.com-20070712021235-0a3tqhdt9nxk0x9y
Merge uses Tree._iter_changes (Aaron Bentley)

Show diffs side-by-side

added added

removed removed

Lines of Context:
45
45
from bzrlib.textfile import check_text_lines
46
46
from bzrlib.trace import mutter, warning, note
47
47
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
48
 
                              FinalPaths, create_by_entry, unique_add,
49
 
                              ROOT_PARENT)
 
48
                              conflict_pass, FinalPaths, create_by_entry,
 
49
                              unique_add, ROOT_PARENT)
50
50
from bzrlib.versionedfile import WeaveMerge
51
51
from bzrlib import ui
52
52
 
114
114
        self.ignore_zero = False
115
115
        self.backup_files = False
116
116
        self.interesting_ids = None
 
117
        self.interesting_files = None
117
118
        self.show_base = False
118
119
        self.reprocess = False
119
120
        self._pb = pb
168
169
            self.this_rev_id = self.this_basis
169
170
 
170
171
    def set_interesting_files(self, file_list):
171
 
        try:
172
 
            self._set_interesting_files(file_list)
173
 
        except NotVersionedError, e:
174
 
            raise BzrCommandError("%s is not a source file in any"
175
 
                                      " tree." % e.path)
 
172
        self.interesting_files = file_list
176
173
 
177
174
    def _set_interesting_files(self, file_list):
178
175
        """Set the list of interesting ids from a list of files."""
294
291
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
295
292
                  'other_tree': self.other_tree,
296
293
                  'interesting_ids': self.interesting_ids,
 
294
                  'interesting_files': self.interesting_files,
297
295
                  'pp': self.pp}
298
296
        if self.merge_type.requires_base:
299
297
            kwargs['base_tree'] = self.base_tree
423
421
    supports_reprocess = True
424
422
    supports_show_base = True
425
423
    history_based = False
 
424
    winner_idx = {"this": 2, "other": 1, "conflict": 1}
426
425
 
427
426
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
428
427
                 interesting_ids=None, reprocess=False, show_base=False,
429
 
                 pb=DummyProgress(), pp=None, change_reporter=None):
430
 
        """Initialize the merger object and perform the merge."""
 
428
                 pb=DummyProgress(), pp=None, change_reporter=None,
 
429
                 interesting_files=None):
 
430
        """Initialize the merger object and perform the merge.
 
431
 
 
432
        :param working_tree: The working tree to apply the merge to
 
433
        :param this_tree: The local tree in the merge operation
 
434
        :param base_tree: The common tree in the merge operation
 
435
        :param other_tree: The other other tree to merge changes from
 
436
        :param interesting_ids: The file_ids of files that should be
 
437
            participate in the merge.  May not be combined with
 
438
            interesting_files.
 
439
        :param: reprocess If True, perform conflict-reduction processing.
 
440
        :param show_base: If True, show the base revision in text conflicts.
 
441
            (incompatible with reprocess)
 
442
        :param pb: A Progress bar
 
443
        :param pp: A ProgressPhase object
 
444
        :param change_reporter: An object that should report changes made
 
445
        :param interesting_files: The tree-relative paths of files that should
 
446
            participate in the merge.  If these paths refer to directories,
 
447
            the contents of those directories will also be included.  May not
 
448
            be combined with interesting_ids.  If neither interesting_files nor
 
449
            interesting_ids is specified, all files may participate in the
 
450
            merge.
 
451
        """
431
452
        object.__init__(self)
 
453
        if interesting_files is not None:
 
454
            assert interesting_ids is None
 
455
        self.interesting_ids = interesting_ids
 
456
        self.interesting_files = interesting_files
432
457
        self.this_tree = working_tree
433
458
        self.this_tree.lock_tree_write()
434
459
        self.base_tree = base_tree
445
470
        if self.pp is None:
446
471
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
447
472
 
448
 
        if interesting_ids is not None:
449
 
            all_ids = interesting_ids
450
 
        else:
451
 
            all_ids = set(base_tree)
452
 
            all_ids.update(other_tree)
453
473
        self.tt = TreeTransform(working_tree, self.pb)
454
474
        try:
455
475
            self.pp.next_phase()
 
476
            entries = self._entries3()
456
477
            child_pb = ui.ui_factory.nested_progress_bar()
457
478
            try:
458
 
                for num, file_id in enumerate(all_ids):
459
 
                    child_pb.update('Preparing file merge', num, len(all_ids))
460
 
                    self.merge_names(file_id)
461
 
                    file_status = self.merge_contents(file_id)
462
 
                    self.merge_executable(file_id, file_status)
 
479
                for num, (file_id, changed, parents3, names3,
 
480
                          executable3) in enumerate(entries):
 
481
                    child_pb.update('Preparing file merge', num, len(entries))
 
482
                    self._merge_names(file_id, parents3, names3)
 
483
                    if changed:
 
484
                        file_status = self.merge_contents(file_id)
 
485
                    else:
 
486
                        file_status = 'unmodified'
 
487
                    self._merge_executable(file_id,
 
488
                        executable3, file_status)
463
489
            finally:
464
490
                child_pb.finished()
465
491
            self.fix_root()
466
492
            self.pp.next_phase()
467
493
            child_pb = ui.ui_factory.nested_progress_bar()
468
494
            try:
469
 
                fs_conflicts = resolve_conflicts(self.tt, child_pb)
 
495
                fs_conflicts = resolve_conflicts(self.tt, child_pb,
 
496
                    lambda t, c: conflict_pass(t, c, self.other_tree))
470
497
            finally:
471
498
                child_pb.finished()
472
499
            if change_reporter is not None:
489
516
            self.this_tree.unlock()
490
517
            self.pb.clear()
491
518
 
 
519
    def _entries3(self):
 
520
        """Gather data about files modified between three trees.
 
521
 
 
522
        Return a list of tuples of file_id, changed, parents3, names3,
 
523
        executable3.  changed is a boolean indicating whether the file contents
 
524
        or kind were changed.  parents3 is a tuple of parent ids for base,
 
525
        other and this.  names3 is a tuple of names for base, other and this.
 
526
        executable3 is a tuple of execute-bit values for base, other and this.
 
527
        """
 
528
        result = []
 
529
        iterator = self.other_tree._iter_changes(self.base_tree,
 
530
                include_unchanged=True, specific_files=self.interesting_files,
 
531
                extra_trees=[self.this_tree])
 
532
        for (file_id, paths, changed, versioned, parents, names, kind,
 
533
             executable) in iterator:
 
534
            if (self.interesting_ids is not None and
 
535
                file_id not in self.interesting_ids):
 
536
                continue
 
537
            if file_id in self.this_tree.inventory:
 
538
                entry = self.this_tree.inventory[file_id]
 
539
                this_name = entry.name
 
540
                this_parent = entry.parent_id
 
541
                this_executable = entry.executable
 
542
            else:
 
543
                this_name = None
 
544
                this_parent = None
 
545
                this_executable = None
 
546
            parents3 = parents + (this_parent,)
 
547
            names3 = names + (this_name,)
 
548
            executable3 = executable + (this_executable,)
 
549
            result.append((file_id, changed, parents3, names3, executable3))
 
550
        return result
 
551
 
492
552
    def fix_root(self):
493
553
        try:
494
554
            self.tt.final_kind(self.tt.root)
566
626
        return tree.kind(file_id)
567
627
 
568
628
    @staticmethod
 
629
    def _three_way(base, other, this):
 
630
        #if base == other, either they all agree, or only THIS has changed.
 
631
        if base == other:
 
632
            return 'this'
 
633
        elif this not in (base, other):
 
634
            return 'conflict'
 
635
        # "Ambiguous clean merge" -- both sides have made the same change.
 
636
        elif this == other:
 
637
            return "this"
 
638
        # this == base: only other has changed.
 
639
        else:
 
640
            return "other"
 
641
 
 
642
    @staticmethod
569
643
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
570
644
        """Do a three-way test on a scalar.
571
645
        Return "this", "other" or "conflict", depending whether a value wins.
586
660
            return "other"
587
661
 
588
662
    def merge_names(self, file_id):
589
 
        """Perform a merge on file_id names and parents"""
590
663
        def get_entry(tree):
591
664
            if file_id in tree.inventory:
592
665
                return tree.inventory[file_id]
595
668
        this_entry = get_entry(self.this_tree)
596
669
        other_entry = get_entry(self.other_tree)
597
670
        base_entry = get_entry(self.base_tree)
598
 
        name_winner = self.scalar_three_way(this_entry, base_entry, 
599
 
                                            other_entry, file_id, self.name)
600
 
        parent_id_winner = self.scalar_three_way(this_entry, base_entry, 
601
 
                                                 other_entry, file_id, 
602
 
                                                 self.parent)
603
 
        if this_entry is None:
 
671
        entries = (base_entry, other_entry, this_entry)
 
672
        names = []
 
673
        parents = []
 
674
        for entry in entries:
 
675
            if entry is None:
 
676
                names.append(None)
 
677
                parents.append(None)
 
678
            else:
 
679
                names.append(entry.name)
 
680
                parents.append(entry.parent_id)
 
681
        return self._merge_names(file_id, parents, names)
 
682
 
 
683
    def _merge_names(self, file_id, parents, names):
 
684
        """Perform a merge on file_id names and parents"""
 
685
        base_name, other_name, this_name = names
 
686
        base_parent, other_parent, this_parent = parents
 
687
 
 
688
        name_winner = self._three_way(*names)
 
689
 
 
690
        parent_id_winner = self._three_way(*parents)
 
691
        if this_name is None:
604
692
            if name_winner == "this":
605
693
                name_winner = "other"
606
694
            if parent_id_winner == "this":
610
698
        if name_winner == "conflict":
611
699
            trans_id = self.tt.trans_id_file_id(file_id)
612
700
            self._raw_conflicts.append(('name conflict', trans_id, 
613
 
                                        self.name(this_entry, file_id), 
614
 
                                        self.name(other_entry, file_id)))
 
701
                                        this_name, other_name))
615
702
        if parent_id_winner == "conflict":
616
703
            trans_id = self.tt.trans_id_file_id(file_id)
617
704
            self._raw_conflicts.append(('parent conflict', trans_id, 
618
 
                                        self.parent(this_entry, file_id), 
619
 
                                        self.parent(other_entry, file_id)))
620
 
        if other_entry is None:
 
705
                                        this_parent, other_parent))
 
706
        if other_name is None:
621
707
            # it doesn't matter whether the result was 'other' or 
622
708
            # 'conflict'-- if there's no 'other', we leave it alone.
623
709
            return
624
710
        # if we get here, name_winner and parent_winner are set to safe values.
625
 
        winner_entry = {"this": this_entry, "other": other_entry, 
626
 
                        "conflict": other_entry}
627
711
        trans_id = self.tt.trans_id_file_id(file_id)
628
 
        parent_id = winner_entry[parent_id_winner].parent_id
 
712
        parent_id = parents[self.winner_idx[parent_id_winner]]
629
713
        if parent_id is not None:
630
714
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
631
 
            self.tt.adjust_path(winner_entry[name_winner].name, 
 
715
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
632
716
                                parent_trans_id, trans_id)
633
717
 
634
718
    def merge_contents(self, file_id):
794
878
 
795
879
    def merge_executable(self, file_id, file_status):
796
880
        """Perform a merge on the execute bit."""
 
881
        executable = [self.executable(t, file_id) for t in (self.base_tree,
 
882
                      self.other_tree, self.this_tree)]
 
883
        self._merge_executable(file_id, executable, file_status)
 
884
 
 
885
    def _merge_executable(self, file_id, executable, file_status):
 
886
        """Perform a merge on the execute bit."""
 
887
        base_executable, other_executable, this_executable = executable
797
888
        if file_status == "deleted":
798
889
            return
799
890
        trans_id = self.tt.trans_id_file_id(file_id)
802
893
                return
803
894
        except NoSuchFile:
804
895
            return
805
 
        winner = self.scalar_three_way(self.this_tree, self.base_tree, 
806
 
                                       self.other_tree, file_id, 
807
 
                                       self.executable)
 
896
        winner = self._three_way(*executable)
808
897
        if winner == "conflict":
809
898
        # There must be a None in here, if we have a conflict, but we
810
899
        # need executability since file status was not deleted.
814
903
                winner = "other"
815
904
        if winner == "this":
816
905
            if file_status == "modified":
817
 
                executability = self.this_tree.is_executable(file_id)
 
906
                executability = this_executable
818
907
                if executability is not None:
819
908
                    trans_id = self.tt.trans_id_file_id(file_id)
820
909
                    self.tt.set_executability(executability, trans_id)
821
910
        else:
822
911
            assert winner == "other"
823
912
            if file_id in self.other_tree:
824
 
                executability = self.other_tree.is_executable(file_id)
 
913
                executability = other_executable
825
914
            elif file_id in self.this_tree:
826
 
                executability = self.this_tree.is_executable(file_id)
 
915
                executability = this_executable
827
916
            elif file_id in self.base_tree:
828
 
                executability = self.base_tree.is_executable(file_id)
 
917
                executability = base_executable
829
918
            if executability is not None:
830
919
                trans_id = self.tt.trans_id_file_id(file_id)
831
920
                self.tt.set_executability(executability, trans_id)
897
986
 
898
987
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
899
988
                 interesting_ids=None, pb=DummyProgress(), pp=None,
900
 
                 reprocess=False, change_reporter=None):
 
989
                 reprocess=False, change_reporter=None,
 
990
                 interesting_files=None):
901
991
        self.this_revision_tree = self._get_revision_tree(this_tree)
902
992
        self.other_revision_tree = self._get_revision_tree(other_tree)
903
993
        super(WeaveMerger, self).__init__(working_tree, this_tree, 
1031
1121
    if interesting_files:
1032
1122
        assert not interesting_ids, ('Only supply interesting_ids'
1033
1123
                                     ' or interesting_files')
1034
 
        merger._set_interesting_files(interesting_files)
 
1124
        merger.interesting_files = interesting_files
1035
1125
    merger.show_base = show_base
1036
1126
    merger.reprocess = reprocess
1037
1127
    merger.other_rev_id = other_rev_id