~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

merge dirstate

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
"""
19
19
 
20
20
import os
 
21
from collections import deque
21
22
from cStringIO import StringIO
22
23
 
23
24
import bzrlib
29
30
from bzrlib.decorators import needs_read_lock
30
31
from bzrlib.errors import BzrError, BzrCheckError
31
32
from bzrlib import errors
32
 
from bzrlib.inventory import Inventory
 
33
from bzrlib.inventory import Inventory, InventoryFile
33
34
from bzrlib.inter import InterObject
34
35
from bzrlib.osutils import fingerprint_file
35
36
import bzrlib.revision
57
58
    """
58
59
    
59
60
    def changes_from(self, other, want_unchanged=False, specific_files=None,
60
 
        extra_trees=None, require_versioned=False, include_root=False):
 
61
        extra_trees=None, require_versioned=False, include_root=False,
 
62
        want_unversioned=False):
61
63
        """Return a TreeDelta of the changes from other to this tree.
62
64
 
63
65
        :param other: A tree to compare with.
72
74
        :param require_versioned: An optional boolean (defaults to False). When
73
75
            supplied and True all the 'specific_files' must be versioned, or
74
76
            a PathsNotVersionedError will be thrown.
 
77
        :param want_unversioned: Scan for unversioned paths.
75
78
 
76
79
        The comparison will be performed by an InterTree object looked up on 
77
80
        self and other.
84
87
            specific_files=specific_files,
85
88
            extra_trees=extra_trees,
86
89
            require_versioned=require_versioned,
87
 
            include_root=include_root
 
90
            include_root=include_root,
 
91
            want_unversioned=want_unversioned,
88
92
            )
89
93
 
90
94
    def _iter_changes(self, from_tree, include_unchanged=False,
91
95
                     specific_files=None, pb=None, extra_trees=None,
92
 
                     require_versioned=True):
 
96
                     require_versioned=True, want_unversioned=False):
93
97
        intertree = InterTree.get(from_tree, self)
94
98
        return intertree._iter_changes(include_unchanged, specific_files, pb,
95
 
            extra_trees, require_versioned)
 
99
            extra_trees, require_versioned, want_unversioned=want_unversioned)
96
100
    
97
101
    def conflicts(self):
98
102
        """Get a list of the conflicts in the tree.
101
105
        """
102
106
        return []
103
107
 
 
108
    def extras(self):
 
109
        """For trees that can have unversioned files, return all such paths."""
 
110
        return []
 
111
 
104
112
    def get_parent_ids(self):
105
113
        """Get the parent ids for this tree. 
106
114
 
535
543
 
536
544
    @needs_read_lock
537
545
    def compare(self, want_unchanged=False, specific_files=None,
538
 
        extra_trees=None, require_versioned=False, include_root=False):
 
546
        extra_trees=None, require_versioned=False, include_root=False,
 
547
        want_unversioned=False):
539
548
        """Return the changes from source to target.
540
549
 
541
550
        :return: A TreeDelta.
550
559
        :param require_versioned: An optional boolean (defaults to False). When
551
560
            supplied and True all the 'specific_files' must be versioned, or
552
561
            a PathsNotVersionedError will be thrown.
 
562
        :param want_unversioned: Scan for unversioned paths.
553
563
        """
554
564
        # NB: show_status depends on being able to pass in non-versioned files
555
565
        # and report them as unknown
562
572
        if specific_files and not specific_file_ids:
563
573
            # All files are unversioned, so just return an empty delta
564
574
            # _compare_trees would think we want a complete delta
565
 
            return delta.TreeDelta()
 
575
            result = delta.TreeDelta()
 
576
            fake_entry = InventoryFile('unused', 'unused', 'unused')
 
577
            result.unversioned = [(path, None,
 
578
                self.target._comparison_data(fake_entry, path)[0]) for path in
 
579
                specific_files]
 
580
            return result
566
581
        return delta._compare_trees(self.source, self.target, want_unchanged,
567
 
            specific_files, include_root, extra_trees=extra_trees)
 
582
            specific_files, include_root, extra_trees=extra_trees,
 
583
            want_unversioned=want_unversioned)
568
584
 
569
585
    def _iter_changes(self, include_unchanged=False,
570
586
                      specific_files=None, pb=None, extra_trees=[],
571
 
                      require_versioned=True):
 
587
                      require_versioned=True, want_unversioned=False):
572
588
        """Generate an iterator of changes between trees.
573
589
 
574
590
        A tuple is returned:
592
608
        :param require_versioned: Raise errors.PathsNotVersionedError if a
593
609
            path in the specific_files list is not versioned in one of
594
610
            source, target or extra_trees.
 
611
        :param want_unversioned: Should unversioned files be returned in the
 
612
            output. An unversioned file is defined as one with (False, False)
 
613
            for the versioned pair.
595
614
        """
596
615
        # this must return a sequence rather than a list so that it can hold a
597
616
        # read-lock for the whole time.
599
618
        # TODO: this really only needs to lock the trees not the branches, so
600
619
        # could do with lock_tree_read() -- mbp 20070227
601
620
        result = []
602
 
        self.source.lock_read()
603
 
        self.target.lock_read()
604
 
        try:
605
 
            lookup_trees = [self.source]
606
 
            if extra_trees:
607
 
                 lookup_trees.extend(extra_trees)
608
 
            specific_file_ids = self.target.paths2ids(specific_files,
609
 
                lookup_trees, require_versioned=require_versioned)
610
 
            to_paths = {}
611
 
            from_entries_by_dir = list(self.source.inventory.iter_entries_by_dir(
612
 
                specific_file_ids=specific_file_ids))
613
 
            from_data = dict((e.file_id, (p, e)) for p, e in from_entries_by_dir)
614
 
            to_entries_by_dir = list(self.target.inventory.iter_entries_by_dir(
615
 
                specific_file_ids=specific_file_ids))
616
 
            num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
617
 
            entry_count = 0
618
 
            for to_path, to_entry in to_entries_by_dir:
619
 
                file_id = to_entry.file_id
620
 
                to_paths[file_id] = to_path
621
 
                entry_count += 1
622
 
                changed_content = False
623
 
                from_path, from_entry = from_data.get(file_id, (None, None))
624
 
                from_versioned = (from_entry is not None)
625
 
                if from_entry is not None:
626
 
                    from_versioned = True
627
 
                    from_name = from_entry.name
628
 
                    from_parent = from_entry.parent_id
629
 
                    from_kind, from_executable, from_stat = \
630
 
                        self.source._comparison_data(from_entry, from_path)
631
 
                    entry_count += 1
632
 
                else:
633
 
                    from_versioned = False
634
 
                    from_kind = None
635
 
                    from_parent = None
636
 
                    from_name = None
637
 
                    from_executable = None
638
 
                versioned = (from_versioned, True)
 
621
        ## self.source.lock_read()
 
622
        ## self.target.lock_read()
 
623
        ## try:
 
624
        lookup_trees = [self.source]
 
625
        if extra_trees:
 
626
             lookup_trees.extend(extra_trees)
 
627
        specific_file_ids = self.target.paths2ids(specific_files,
 
628
            lookup_trees, require_versioned=require_versioned)
 
629
        if want_unversioned:
 
630
            all_unversioned = sorted([(p.split('/'), p) for p in self.target.extras()
 
631
                if not specific_files or
 
632
                    osutils.is_inside_any(specific_files, p)])
 
633
            all_unversioned = deque(all_unversioned)
 
634
        else:
 
635
            all_unversioned = deque()
 
636
        to_paths = {}
 
637
        from_entries_by_dir = list(self.source.inventory.iter_entries_by_dir(
 
638
            specific_file_ids=specific_file_ids))
 
639
        from_data = dict((e.file_id, (p, e)) for p, e in from_entries_by_dir)
 
640
        to_entries_by_dir = list(self.target.inventory.iter_entries_by_dir(
 
641
            specific_file_ids=specific_file_ids))
 
642
        num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
 
643
        entry_count = 0
 
644
        # the unversioned path lookup only occurs on real trees - where there 
 
645
        # can be extras. So the fake_entry is solely used to look up
 
646
        # executable it values when execute is not supported.
 
647
        fake_entry = InventoryFile('unused', 'unused', 'unused')
 
648
        for to_path, to_entry in to_entries_by_dir:
 
649
            while all_unversioned and all_unversioned[0][0] < to_path.split('/'):
 
650
                unversioned_path = all_unversioned.popleft()
639
651
                to_kind, to_executable, to_stat = \
640
 
                    self.target._comparison_data(to_entry, to_path)
641
 
                kind = (from_kind, to_kind)
642
 
                if kind[0] != kind[1]:
643
 
                    changed_content = True
644
 
                elif from_kind == 'file':
645
 
                    from_size = self.source._file_size(from_entry, from_stat)
646
 
                    to_size = self.target._file_size(to_entry, to_stat)
647
 
                    if from_size != to_size:
648
 
                        changed_content = True
649
 
                    elif (self.source.get_file_sha1(file_id, from_path, from_stat) !=
650
 
                        self.target.get_file_sha1(file_id, to_path, to_stat)):
651
 
                        changed_content = True
652
 
                elif from_kind == 'symlink':
653
 
                    if (self.source.get_symlink_target(file_id) != 
654
 
                        self.target.get_symlink_target(file_id)):
655
 
                        changed_content = True
 
652
                    self.target._comparison_data(fake_entry, unversioned_path[1])
 
653
                yield (None, unversioned_path[1], True, (False, False),
 
654
                    (None, None),
 
655
                    (None, unversioned_path[0][-1]),
 
656
                    (None, to_kind),
 
657
                    (None, to_executable))
 
658
            file_id = to_entry.file_id
 
659
            to_paths[file_id] = to_path
 
660
            entry_count += 1
 
661
            changed_content = False
 
662
            from_path, from_entry = from_data.get(file_id, (None, None))
 
663
            from_versioned = (from_entry is not None)
 
664
            if from_entry is not None:
 
665
                from_versioned = True
 
666
                from_name = from_entry.name
 
667
                from_parent = from_entry.parent_id
 
668
                from_kind, from_executable, from_stat = \
 
669
                    self.source._comparison_data(from_entry, from_path)
 
670
                entry_count += 1
 
671
            else:
 
672
                from_versioned = False
 
673
                from_kind = None
 
674
                from_parent = None
 
675
                from_name = None
 
676
                from_executable = None
 
677
            versioned = (from_versioned, True)
 
678
            to_kind, to_executable, to_stat = \
 
679
                self.target._comparison_data(to_entry, to_path)
 
680
            kind = (from_kind, to_kind)
 
681
            if kind[0] != kind[1]:
 
682
                changed_content = True
 
683
            elif from_kind == 'file':
 
684
                from_size = self.source._file_size(from_entry, from_stat)
 
685
                to_size = self.target._file_size(to_entry, to_stat)
 
686
                if from_size != to_size:
 
687
                    changed_content = True
 
688
                elif (self.source.get_file_sha1(file_id, from_path, from_stat) !=
 
689
                    self.target.get_file_sha1(file_id, to_path, to_stat)):
 
690
                    changed_content = True
 
691
            elif from_kind == 'symlink':
 
692
                if (self.source.get_symlink_target(file_id) !=
 
693
                    self.target.get_symlink_target(file_id)):
 
694
                    changed_content = True
656
695
                elif from_kind == 'tree-reference':
657
696
                    if (self.source.get_reference_revision(from_entry, from_path)
658
697
                        != self.target.get_reference_revision(to_entry, to_path)):
659
698
                        changed_content = True 
660
 
                parent = (from_parent, to_entry.parent_id)
661
 
                name = (from_name, to_entry.name)
662
 
                executable = (from_executable, to_executable)
663
 
                if pb is not None:
664
 
                    pb.update('comparing files', entry_count, num_entries)
665
 
                if (changed_content is not False or versioned[0] != versioned[1] 
666
 
                    or parent[0] != parent[1] or name[0] != name[1] or 
667
 
                    executable[0] != executable[1] or include_unchanged):
668
 
                    result.append((file_id, to_path, changed_content, versioned, parent,
669
 
                           name, kind, executable))
670
 
            def get_to_path(from_entry):
671
 
                if from_entry.parent_id is None:
672
 
                    to_path = ''
673
 
                else:
674
 
                    if from_entry.parent_id not in to_paths:
675
 
                        get_to_path(self.source.inventory[from_entry.parent_id])
676
 
                    to_path = osutils.pathjoin(to_paths[from_entry.parent_id],
677
 
                                               from_entry.name)
678
 
                to_paths[from_entry.file_id] = to_path
679
 
                return to_path
680
 
 
681
 
            for path, from_entry in from_entries_by_dir:
682
 
                file_id = from_entry.file_id
683
 
                if file_id in to_paths:
684
 
                    continue
685
 
                to_path = get_to_path(from_entry)
686
 
                entry_count += 1
687
 
                if pb is not None:
688
 
                    pb.update('comparing files', entry_count, num_entries)
689
 
                versioned = (True, False)
690
 
                parent = (from_entry.parent_id, None)
691
 
                name = (from_entry.name, None)
692
 
                from_kind, from_executable, stat_value = \
693
 
                    self.source._comparison_data(from_entry, path)
694
 
                kind = (from_kind, None)
695
 
                executable = (from_executable, None)
696
 
                changed_content = True
697
 
                # the parent's path is necessarily known at this point.
698
 
                result.append((file_id, to_path, changed_content, versioned, parent,
699
 
                      name, kind, executable))
700
 
        finally:
701
 
            self.source.unlock()
702
 
            self.target.unlock()
703
 
        return result
 
699
            parent = (from_parent, to_entry.parent_id)
 
700
            name = (from_name, to_entry.name)
 
701
            executable = (from_executable, to_executable)
 
702
            if pb is not None:
 
703
                pb.update('comparing files', entry_count, num_entries)
 
704
            if (changed_content is not False or versioned[0] != versioned[1]
 
705
                or parent[0] != parent[1] or name[0] != name[1] or 
 
706
                executable[0] != executable[1] or include_unchanged):
 
707
                yield (file_id, to_path, changed_content, versioned, parent,
 
708
                       name, kind, executable)
 
709
        while all_unversioned:
 
710
            # yield any trailing unversioned paths
 
711
            unversioned_path = all_unversioned.popleft()
 
712
            to_kind, to_executable, to_stat = \
 
713
                self.target._comparison_data(fake_entry, unversioned_path[1])
 
714
            yield (None, unversioned_path[1], True, (False, False),
 
715
                (None, None),
 
716
                (None, unversioned_path[0][-1]),
 
717
                (None, to_kind),
 
718
                (None, to_executable))
 
719
 
 
720
        def get_to_path(from_entry):
 
721
            if from_entry.parent_id is None:
 
722
                to_path = ''
 
723
            else:
 
724
                if from_entry.parent_id not in to_paths:
 
725
                    get_to_path(self.source.inventory[from_entry.parent_id])
 
726
                to_path = osutils.pathjoin(to_paths[from_entry.parent_id],
 
727
                                           from_entry.name)
 
728
            to_paths[from_entry.file_id] = to_path
 
729
            return to_path
 
730
 
 
731
        for path, from_entry in from_entries_by_dir:
 
732
            file_id = from_entry.file_id
 
733
            if file_id in to_paths:
 
734
                continue
 
735
            to_path = get_to_path(from_entry)
 
736
            entry_count += 1
 
737
            if pb is not None:
 
738
                pb.update('comparing files', entry_count, num_entries)
 
739
            versioned = (True, False)
 
740
            parent = (from_entry.parent_id, None)
 
741
            name = (from_entry.name, None)
 
742
            from_kind, from_executable, stat_value = \
 
743
                self.source._comparison_data(from_entry, path)
 
744
            kind = (from_kind, None)
 
745
            executable = (from_executable, None)
 
746
            changed_content = True
 
747
            # the parent's path is necessarily known at this point.
 
748
            yield(file_id, to_path, changed_content, versioned, parent,
 
749
                  name, kind, executable)
704
750
 
705
751
 
706
752
# This was deprecated before 0.12, but did not have an official warning