~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: John Arbash Meinel
  • Date: 2007-07-13 02:23:34 UTC
  • mfrom: (2592 +trunk) (2612 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2614.
  • Revision ID: john@arbash-meinel.com-20070713022334-qb6ewgo6v4251yd9
[merge] bzr.dev 2612

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
from bzrlib import (
23
23
    osutils,
24
24
    registry,
 
25
    revision as _mod_revision,
25
26
    )
26
27
from bzrlib.branch import Branch
27
28
from bzrlib.conflicts import ConflictList, Conflict
45
46
from bzrlib.textfile import check_text_lines
46
47
from bzrlib.trace import mutter, warning, note
47
48
from bzrlib.transform import (TreeTransform, resolve_conflicts, cook_conflicts,
48
 
                              FinalPaths, create_by_entry, unique_add,
49
 
                              ROOT_PARENT)
 
49
                              conflict_pass, FinalPaths, create_by_entry,
 
50
                              unique_add, ROOT_PARENT)
50
51
from bzrlib.versionedfile import WeaveMerge
51
52
from bzrlib import ui
52
53
 
103
104
        object.__init__(self)
104
105
        assert this_tree is not None, "this_tree is required"
105
106
        self.this_branch = this_branch
106
 
        self.this_basis = this_branch.last_revision()
 
107
        self.this_basis = _mod_revision.ensure_null(
 
108
            this_branch.last_revision())
107
109
        self.this_rev_id = None
108
110
        self.this_tree = this_tree
109
111
        self.this_revision_tree = None
114
116
        self.ignore_zero = False
115
117
        self.backup_files = False
116
118
        self.interesting_ids = None
 
119
        self.interesting_files = None
117
120
        self.show_base = False
118
121
        self.reprocess = False
119
122
        self._pb = pb
168
171
            self.this_rev_id = self.this_basis
169
172
 
170
173
    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)
 
174
        self.interesting_files = file_list
176
175
 
177
176
    def _set_interesting_files(self, file_list):
178
177
        """Set the list of interesting ids from a list of files."""
217
216
        self.other_branch, self.other_tree = _get_tree(other_revision,
218
217
                                                  self.this_branch)
219
218
        if other_revision[1] == -1:
220
 
            self.other_rev_id = self.other_branch.last_revision()
221
 
            if self.other_rev_id is None:
 
219
            self.other_rev_id = _mod_revision.ensure_null(
 
220
                self.other_branch.last_revision())
 
221
            if _mod_revision.is_null(self.other_rev_id):
222
222
                raise NoCommits(self.other_branch)
223
223
            self.other_basis = self.other_rev_id
224
224
        elif other_revision[1] is not None:
281
281
            if base_revision[1] == -1:
282
282
                self.base_rev_id = base_branch.last_revision()
283
283
            elif base_revision[1] is None:
284
 
                self.base_rev_id = None
 
284
                self.base_rev_id = _mod_revision.NULL_REVISION
285
285
            else:
286
 
                self.base_rev_id = base_branch.get_rev_id(base_revision[1])
 
286
                self.base_rev_id = _mod_revision.ensure_null(
 
287
                    base_branch.get_rev_id(base_revision[1]))
287
288
            if self.this_branch.base != base_branch.base:
288
289
                self.this_branch.fetch(base_branch)
289
290
            self.base_is_ancestor = is_ancestor(self.this_basis, 
294
295
        kwargs = {'working_tree':self.this_tree, 'this_tree': self.this_tree,
295
296
                  'other_tree': self.other_tree,
296
297
                  'interesting_ids': self.interesting_ids,
 
298
                  'interesting_files': self.interesting_files,
297
299
                  'pp': self.pp}
298
300
        if self.merge_type.requires_base:
299
301
            kwargs['base_tree'] = self.base_tree
423
425
    supports_reprocess = True
424
426
    supports_show_base = True
425
427
    history_based = False
 
428
    winner_idx = {"this": 2, "other": 1, "conflict": 1}
426
429
 
427
430
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
428
431
                 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."""
 
432
                 pb=DummyProgress(), pp=None, change_reporter=None,
 
433
                 interesting_files=None):
 
434
        """Initialize the merger object and perform the merge.
 
435
 
 
436
        :param working_tree: The working tree to apply the merge to
 
437
        :param this_tree: The local tree in the merge operation
 
438
        :param base_tree: The common tree in the merge operation
 
439
        :param other_tree: The other other tree to merge changes from
 
440
        :param interesting_ids: The file_ids of files that should be
 
441
            participate in the merge.  May not be combined with
 
442
            interesting_files.
 
443
        :param: reprocess If True, perform conflict-reduction processing.
 
444
        :param show_base: If True, show the base revision in text conflicts.
 
445
            (incompatible with reprocess)
 
446
        :param pb: A Progress bar
 
447
        :param pp: A ProgressPhase object
 
448
        :param change_reporter: An object that should report changes made
 
449
        :param interesting_files: The tree-relative paths of files that should
 
450
            participate in the merge.  If these paths refer to directories,
 
451
            the contents of those directories will also be included.  May not
 
452
            be combined with interesting_ids.  If neither interesting_files nor
 
453
            interesting_ids is specified, all files may participate in the
 
454
            merge.
 
455
        """
431
456
        object.__init__(self)
 
457
        if interesting_files is not None:
 
458
            assert interesting_ids is None
 
459
        self.interesting_ids = interesting_ids
 
460
        self.interesting_files = interesting_files
432
461
        self.this_tree = working_tree
433
462
        self.this_tree.lock_tree_write()
434
463
        self.base_tree = base_tree
445
474
        if self.pp is None:
446
475
            self.pp = ProgressPhase("Merge phase", 3, self.pb)
447
476
 
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
477
        self.tt = TreeTransform(working_tree, self.pb)
454
478
        try:
455
479
            self.pp.next_phase()
 
480
            entries = self._entries3()
456
481
            child_pb = ui.ui_factory.nested_progress_bar()
457
482
            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)
 
483
                for num, (file_id, changed, parents3, names3,
 
484
                          executable3) in enumerate(entries):
 
485
                    child_pb.update('Preparing file merge', num, len(entries))
 
486
                    self._merge_names(file_id, parents3, names3)
 
487
                    if changed:
 
488
                        file_status = self.merge_contents(file_id)
 
489
                    else:
 
490
                        file_status = 'unmodified'
 
491
                    self._merge_executable(file_id,
 
492
                        executable3, file_status)
463
493
            finally:
464
494
                child_pb.finished()
465
495
            self.fix_root()
466
496
            self.pp.next_phase()
467
497
            child_pb = ui.ui_factory.nested_progress_bar()
468
498
            try:
469
 
                fs_conflicts = resolve_conflicts(self.tt, child_pb)
 
499
                fs_conflicts = resolve_conflicts(self.tt, child_pb,
 
500
                    lambda t, c: conflict_pass(t, c, self.other_tree))
470
501
            finally:
471
502
                child_pb.finished()
472
503
            if change_reporter is not None:
489
520
            self.this_tree.unlock()
490
521
            self.pb.clear()
491
522
 
 
523
    def _entries3(self):
 
524
        """Gather data about files modified between three trees.
 
525
 
 
526
        Return a list of tuples of file_id, changed, parents3, names3,
 
527
        executable3.  changed is a boolean indicating whether the file contents
 
528
        or kind were changed.  parents3 is a tuple of parent ids for base,
 
529
        other and this.  names3 is a tuple of names for base, other and this.
 
530
        executable3 is a tuple of execute-bit values for base, other and this.
 
531
        """
 
532
        result = []
 
533
        iterator = self.other_tree._iter_changes(self.base_tree,
 
534
                include_unchanged=True, specific_files=self.interesting_files,
 
535
                extra_trees=[self.this_tree])
 
536
        for (file_id, paths, changed, versioned, parents, names, kind,
 
537
             executable) in iterator:
 
538
            if (self.interesting_ids is not None and
 
539
                file_id not in self.interesting_ids):
 
540
                continue
 
541
            if file_id in self.this_tree.inventory:
 
542
                entry = self.this_tree.inventory[file_id]
 
543
                this_name = entry.name
 
544
                this_parent = entry.parent_id
 
545
                this_executable = entry.executable
 
546
            else:
 
547
                this_name = None
 
548
                this_parent = None
 
549
                this_executable = None
 
550
            parents3 = parents + (this_parent,)
 
551
            names3 = names + (this_name,)
 
552
            executable3 = executable + (this_executable,)
 
553
            result.append((file_id, changed, parents3, names3, executable3))
 
554
        return result
 
555
 
492
556
    def fix_root(self):
493
557
        try:
494
558
            self.tt.final_kind(self.tt.root)
566
630
        return tree.kind(file_id)
567
631
 
568
632
    @staticmethod
 
633
    def _three_way(base, other, this):
 
634
        #if base == other, either they all agree, or only THIS has changed.
 
635
        if base == other:
 
636
            return 'this'
 
637
        elif this not in (base, other):
 
638
            return 'conflict'
 
639
        # "Ambiguous clean merge" -- both sides have made the same change.
 
640
        elif this == other:
 
641
            return "this"
 
642
        # this == base: only other has changed.
 
643
        else:
 
644
            return "other"
 
645
 
 
646
    @staticmethod
569
647
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
570
648
        """Do a three-way test on a scalar.
571
649
        Return "this", "other" or "conflict", depending whether a value wins.
586
664
            return "other"
587
665
 
588
666
    def merge_names(self, file_id):
589
 
        """Perform a merge on file_id names and parents"""
590
667
        def get_entry(tree):
591
668
            if file_id in tree.inventory:
592
669
                return tree.inventory[file_id]
595
672
        this_entry = get_entry(self.this_tree)
596
673
        other_entry = get_entry(self.other_tree)
597
674
        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:
 
675
        entries = (base_entry, other_entry, this_entry)
 
676
        names = []
 
677
        parents = []
 
678
        for entry in entries:
 
679
            if entry is None:
 
680
                names.append(None)
 
681
                parents.append(None)
 
682
            else:
 
683
                names.append(entry.name)
 
684
                parents.append(entry.parent_id)
 
685
        return self._merge_names(file_id, parents, names)
 
686
 
 
687
    def _merge_names(self, file_id, parents, names):
 
688
        """Perform a merge on file_id names and parents"""
 
689
        base_name, other_name, this_name = names
 
690
        base_parent, other_parent, this_parent = parents
 
691
 
 
692
        name_winner = self._three_way(*names)
 
693
 
 
694
        parent_id_winner = self._three_way(*parents)
 
695
        if this_name is None:
604
696
            if name_winner == "this":
605
697
                name_winner = "other"
606
698
            if parent_id_winner == "this":
610
702
        if name_winner == "conflict":
611
703
            trans_id = self.tt.trans_id_file_id(file_id)
612
704
            self._raw_conflicts.append(('name conflict', trans_id, 
613
 
                                        self.name(this_entry, file_id), 
614
 
                                        self.name(other_entry, file_id)))
 
705
                                        this_name, other_name))
615
706
        if parent_id_winner == "conflict":
616
707
            trans_id = self.tt.trans_id_file_id(file_id)
617
708
            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:
 
709
                                        this_parent, other_parent))
 
710
        if other_name is None:
621
711
            # it doesn't matter whether the result was 'other' or 
622
712
            # 'conflict'-- if there's no 'other', we leave it alone.
623
713
            return
624
714
        # 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
715
        trans_id = self.tt.trans_id_file_id(file_id)
628
 
        parent_id = winner_entry[parent_id_winner].parent_id
 
716
        parent_id = parents[self.winner_idx[parent_id_winner]]
629
717
        if parent_id is not None:
630
718
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
631
 
            self.tt.adjust_path(winner_entry[name_winner].name, 
 
719
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
632
720
                                parent_trans_id, trans_id)
633
721
 
634
722
    def merge_contents(self, file_id):
794
882
 
795
883
    def merge_executable(self, file_id, file_status):
796
884
        """Perform a merge on the execute bit."""
 
885
        executable = [self.executable(t, file_id) for t in (self.base_tree,
 
886
                      self.other_tree, self.this_tree)]
 
887
        self._merge_executable(file_id, executable, file_status)
 
888
 
 
889
    def _merge_executable(self, file_id, executable, file_status):
 
890
        """Perform a merge on the execute bit."""
 
891
        base_executable, other_executable, this_executable = executable
797
892
        if file_status == "deleted":
798
893
            return
799
894
        trans_id = self.tt.trans_id_file_id(file_id)
802
897
                return
803
898
        except NoSuchFile:
804
899
            return
805
 
        winner = self.scalar_three_way(self.this_tree, self.base_tree, 
806
 
                                       self.other_tree, file_id, 
807
 
                                       self.executable)
 
900
        winner = self._three_way(*executable)
808
901
        if winner == "conflict":
809
902
        # There must be a None in here, if we have a conflict, but we
810
903
        # need executability since file status was not deleted.
814
907
                winner = "other"
815
908
        if winner == "this":
816
909
            if file_status == "modified":
817
 
                executability = self.this_tree.is_executable(file_id)
 
910
                executability = this_executable
818
911
                if executability is not None:
819
912
                    trans_id = self.tt.trans_id_file_id(file_id)
820
913
                    self.tt.set_executability(executability, trans_id)
821
914
        else:
822
915
            assert winner == "other"
823
916
            if file_id in self.other_tree:
824
 
                executability = self.other_tree.is_executable(file_id)
 
917
                executability = other_executable
825
918
            elif file_id in self.this_tree:
826
 
                executability = self.this_tree.is_executable(file_id)
 
919
                executability = this_executable
827
920
            elif file_id in self.base_tree:
828
 
                executability = self.base_tree.is_executable(file_id)
 
921
                executability = base_executable
829
922
            if executability is not None:
830
923
                trans_id = self.tt.trans_id_file_id(file_id)
831
924
                self.tt.set_executability(executability, trans_id)
897
990
 
898
991
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
899
992
                 interesting_ids=None, pb=DummyProgress(), pp=None,
900
 
                 reprocess=False, change_reporter=None):
 
993
                 reprocess=False, change_reporter=None,
 
994
                 interesting_files=None):
901
995
        self.this_revision_tree = self._get_revision_tree(this_tree)
902
996
        self.other_revision_tree = self._get_revision_tree(other_tree)
903
997
        super(WeaveMerger, self).__init__(working_tree, this_tree, 
1031
1125
    if interesting_files:
1032
1126
        assert not interesting_ids, ('Only supply interesting_ids'
1033
1127
                                     ' or interesting_files')
1034
 
        merger._set_interesting_files(interesting_files)
 
1128
        merger.interesting_files = interesting_files
1035
1129
    merger.show_base = show_base
1036
1130
    merger.reprocess = reprocess
1037
1131
    merger.other_rev_id = other_rev_id