~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-08-28 09:22:17 UTC
  • mfrom: (3654.1.1 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20080828092217-98wmtek2p8cie8sc
(vila) Fix bug #225020 by catching CURLE_SEND_ERROR

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
# FIXME: This refactoring of the workingtree code doesn't seem to keep
 
17
# FIXME: This refactoring of the workingtree code doesn't seem to keep 
18
18
# the WorkingTree's copy of the inventory in sync with the branch.  The
19
19
# branch modifies its working inventory when it does a commit to make
20
20
# missing files permanently removed.
27
27
# created, but it's not for now.
28
28
ROOT_ID = "TREE_ROOT"
29
29
 
 
30
import os
 
31
import re
 
32
import sys
 
33
 
30
34
from bzrlib.lazy_import import lazy_import
31
35
lazy_import(globals(), """
32
36
import collections
33
 
import copy
34
 
import os
35
 
import re
36
37
import tarfile
37
38
 
38
39
import bzrlib
39
40
from bzrlib import (
40
 
    chk_map,
41
41
    errors,
42
42
    generate_ids,
43
43
    osutils,
44
44
    symbol_versioning,
 
45
    workingtree,
45
46
    )
46
47
""")
47
48
 
68
69
        file_id of the parent directory, or ROOT_ID
69
70
 
70
71
    revision
71
 
        the revision_id in which this variation of this file was
 
72
        the revision_id in which this variation of this file was 
72
73
        introduced.
73
74
 
74
75
    executable
77
78
 
78
79
    text_sha1
79
80
        sha-1 of the text of the file
80
 
 
 
81
        
81
82
    text_size
82
83
        size in bytes of the text of the file
83
 
 
 
84
        
84
85
    (reading a version 4 tree created a text_id field.)
85
86
 
86
87
    >>> i = Inventory()
89
90
    >>> i.add(InventoryDirectory('123', 'src', ROOT_ID))
90
91
    InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
91
92
    >>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
92
 
    InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None, revision=None)
 
93
    InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None)
93
94
    >>> shouldbe = {0: '', 1: 'src', 2: 'src/hello.c'}
94
95
    >>> for ix, j in enumerate(i.iter_entries()):
95
96
    ...   print (j[0] == shouldbe[ix], j[1])
96
 
    ...
 
97
    ... 
97
98
    (True, InventoryDirectory('TREE_ROOT', u'', parent_id=None, revision=None))
98
99
    (True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
99
 
    (True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None, revision=None))
 
100
    (True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None))
100
101
    >>> i.add(InventoryFile('2324', 'bye.c', '123'))
101
 
    InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None, revision=None)
 
102
    InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
102
103
    >>> i.add(InventoryDirectory('2325', 'wibble', '123'))
103
104
    InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
104
105
    >>> i.path2id('src/wibble')
106
107
    >>> '2325' in i
107
108
    True
108
109
    >>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
109
 
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None, revision=None)
 
110
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
110
111
    >>> i['2326']
111
 
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None, revision=None)
 
112
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
112
113
    >>> for path, entry in i.iter_entries():
113
114
    ...     print path
114
 
    ...
 
115
    ... 
115
116
    <BLANKLINE>
116
117
    src
117
118
    src/bye.c
124
125
 
125
126
    # Constants returned by describe_change()
126
127
    #
127
 
    # TODO: These should probably move to some kind of FileChangeDescription
128
 
    # class; that's like what's inside a TreeDelta but we want to be able to
 
128
    # TODO: These should probably move to some kind of FileChangeDescription 
 
129
    # class; that's like what's inside a TreeDelta but we want to be able to 
129
130
    # generate them just for one file at a time.
130
131
    RENAMED = 'renamed'
131
132
    MODIFIED_AND_RENAMED = 'modified and renamed'
132
 
 
 
133
    
133
134
    __slots__ = []
134
135
 
135
136
    def detect_changes(self, old_entry):
136
137
        """Return a (text_modified, meta_modified) from this to old_entry.
137
 
 
138
 
        _read_tree_state must have been called on self and old_entry prior to
 
138
        
 
139
        _read_tree_state must have been called on self and old_entry prior to 
139
140
        calling detect_changes.
140
141
        """
141
142
        return False, False
143
144
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
144
145
             output_to, reverse=False):
145
146
        """Perform a diff between two entries of the same kind."""
146
 
 
 
147
    
147
148
    def parent_candidates(self, previous_inventories):
148
149
        """Find possible per-file graph parents.
149
150
 
162
163
                if ie.revision in candidates:
163
164
                    # same revision value in two different inventories:
164
165
                    # correct possible inconsistencies:
165
 
                    #     * there was a bug in revision updates with 'x' bit
 
166
                    #     * there was a bug in revision updates with 'x' bit 
166
167
                    #       support.
167
168
                    try:
168
169
                        if candidates[ie.revision].executable != ie.executable:
198
199
 
199
200
    def __init__(self, file_id, name, parent_id, text_id=None):
200
201
        """Create an InventoryEntry
201
 
 
 
202
        
202
203
        The filename must be a single component, relative to the
203
204
        parent directory; it cannot be a whole path or relative name.
204
205
 
241
242
    @deprecated_method(deprecated_in((1, 6, 0)))
242
243
    def put_on_disk(self, dest, dp, tree):
243
244
        """Create a representation of self on disk in the prefix dest.
244
 
 
 
245
        
245
246
        This is a template method - implement _put_on_disk in subclasses.
246
247
        """
247
248
        fullpath = osutils.pathjoin(dest, dp)
266
267
        This is a template method, override _check for kind specific
267
268
        tests.
268
269
 
269
 
        :param checker: Check object providing context for the checks;
 
270
        :param checker: Check object providing context for the checks; 
270
271
             can be used to find out what parts of the repository have already
271
272
             been checked.
272
273
        :param rev_id: Revision id from which this InventoryEntry was loaded.
282
283
 
283
284
    def _check(self, checker, rev_id, tree):
284
285
        """Check this inventory entry for kind specific errors."""
285
 
        raise BzrCheckError('unknown entry kind %r in revision {%s}' %
 
286
        raise BzrCheckError('unknown entry kind %r in revision {%s}' % 
286
287
                            (self.kind, rev_id))
287
288
 
288
289
    def copy(self):
292
293
    @staticmethod
293
294
    def describe_change(old_entry, new_entry):
294
295
        """Describe the change between old_entry and this.
295
 
 
 
296
        
296
297
        This smells of being an InterInventoryEntry situation, but as its
297
298
        the first one, we're making it a static method for now.
298
299
 
299
 
        An entry with a different parent, or different name is considered
 
300
        An entry with a different parent, or different name is considered 
300
301
        to be renamed. Reparenting is an internal detail.
301
302
        Note that renaming the parent does not trigger a rename for the
302
303
        child entry itself.
340
341
                   self.revision))
341
342
 
342
343
    def __eq__(self, other):
343
 
        if other is self:
344
 
            # For the case when objects are cached
345
 
            return True
346
344
        if not isinstance(other, InventoryEntry):
347
345
            return NotImplemented
348
346
 
383
381
 
384
382
    def _read_tree_state(self, path, work_tree):
385
383
        """Populate fields in the inventory entry from the given tree.
386
 
 
 
384
        
387
385
        Note that this should be modified to be a noop on virtual trees
388
386
        as all entries created there are prepopulated.
389
387
        """
390
 
        # TODO: Rather than running this manually, we should check the
 
388
        # TODO: Rather than running this manually, we should check the 
391
389
        # working sha1 and other expensive properties when they're
392
390
        # first requested, or preload them if they're already known
393
391
        pass            # nothing to do by default
419
417
    def __eq__(self, other):
420
418
        if not isinstance(other, RootEntry):
421
419
            return NotImplemented
422
 
 
 
420
        
423
421
        return (self.file_id == other.file_id) \
424
422
               and (self.children == other.children)
425
423
 
488
486
                checker.repeated_text_cnt += 1
489
487
                return
490
488
 
 
489
        mutter('check version {%s} of {%s}', tree_revision_id, self.file_id)
491
490
        checker.checked_text_cnt += 1
492
491
        # We can't check the length, because Weave doesn't store that
493
492
        # information, and the whole point of looking at the weave's
565
564
        self.executable = work_tree.is_executable(self.file_id, path=path)
566
565
 
567
566
    def __repr__(self):
568
 
        return ("%s(%r, %r, parent_id=%r, sha1=%r, len=%s, revision=%s)"
 
567
        return ("%s(%r, %r, parent_id=%r, sha1=%r, len=%s)"
569
568
                % (self.__class__.__name__,
570
569
                   self.file_id,
571
570
                   self.name,
572
571
                   self.parent_id,
573
572
                   self.text_sha1,
574
 
                   self.text_size,
575
 
                   self.revision))
 
573
                   self.text_size))
576
574
 
577
575
    def _forget_tree_state(self):
578
576
        self.text_sha1 = None
681
679
 
682
680
 
683
681
class TreeReference(InventoryEntry):
684
 
 
 
682
    
685
683
    kind = 'tree-reference'
686
 
 
 
684
    
687
685
    def __init__(self, file_id, name, parent_id, revision=None,
688
686
                 reference_revision=None):
689
687
        InventoryEntry.__init__(self, file_id, name, parent_id)
701
699
            self.file_id, path)
702
700
 
703
701
    def _forget_tree_state(self):
704
 
        self.reference_revision = None
 
702
        self.reference_revision = None 
705
703
 
706
704
    def _unchanged(self, previous_ie):
707
705
        """See InventoryEntry._unchanged."""
711
709
        return compatible
712
710
 
713
711
 
714
 
class CommonInventory(object):
715
 
    """Basic inventory logic, defined in terms of primitives like has_id."""
716
 
 
717
 
    def __contains__(self, file_id):
718
 
        """True if this entry contains a file with given id.
719
 
 
720
 
        >>> inv = Inventory()
721
 
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
722
 
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
723
 
        >>> '123' in inv
724
 
        True
725
 
        >>> '456' in inv
726
 
        False
727
 
 
728
 
        Note that this method along with __iter__ are not encouraged for use as
729
 
        they are less clear than specific query methods - they may be rmeoved
730
 
        in the future.
731
 
        """
732
 
        return self.has_id(file_id)
733
 
 
734
 
    def id2path(self, file_id):
735
 
        """Return as a string the path to file_id.
736
 
 
737
 
        >>> i = Inventory()
738
 
        >>> e = i.add(InventoryDirectory('src-id', 'src', ROOT_ID))
739
 
        >>> e = i.add(InventoryFile('foo-id', 'foo.c', parent_id='src-id'))
740
 
        >>> print i.id2path('foo-id')
741
 
        src/foo.c
742
 
        """
743
 
        # get all names, skipping root
744
 
        return '/'.join(reversed(
745
 
            [parent.name for parent in
746
 
             self._iter_file_id_parents(file_id)][:-1]))
747
 
 
748
 
    def iter_entries(self, from_dir=None, recursive=True):
749
 
        """Return (path, entry) pairs, in order by name.
750
 
        
751
 
        :param from_dir: if None, start from the root,
752
 
          otherwise start from this directory (either file-id or entry)
753
 
        :param recursive: recurse into directories or not
754
 
        """
755
 
        if from_dir is None:
756
 
            if self.root is None:
757
 
                return
758
 
            from_dir = self.root
759
 
            yield '', self.root
760
 
        elif isinstance(from_dir, basestring):
761
 
            from_dir = self[from_dir]
762
 
 
763
 
        # unrolling the recursive called changed the time from
764
 
        # 440ms/663ms (inline/total) to 116ms/116ms
765
 
        children = from_dir.children.items()
766
 
        children.sort()
767
 
        if not recursive:
768
 
            for name, ie in children:
769
 
                yield name, ie
770
 
            return
771
 
        children = collections.deque(children)
772
 
        stack = [(u'', children)]
773
 
        while stack:
774
 
            from_dir_relpath, children = stack[-1]
775
 
 
776
 
            while children:
777
 
                name, ie = children.popleft()
778
 
 
779
 
                # we know that from_dir_relpath never ends in a slash
780
 
                # and 'f' doesn't begin with one, we can do a string op, rather
781
 
                # than the checks of pathjoin(), though this means that all paths
782
 
                # start with a slash
783
 
                path = from_dir_relpath + '/' + name
784
 
 
785
 
                yield path[1:], ie
786
 
 
787
 
                if ie.kind != 'directory':
788
 
                    continue
789
 
 
790
 
                # But do this child first
791
 
                new_children = ie.children.items()
792
 
                new_children.sort()
793
 
                new_children = collections.deque(new_children)
794
 
                stack.append((path, new_children))
795
 
                # Break out of inner loop, so that we start outer loop with child
796
 
                break
797
 
            else:
798
 
                # if we finished all children, pop it off the stack
799
 
                stack.pop()
800
 
 
801
 
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
802
 
        yield_parents=False):
803
 
        """Iterate over the entries in a directory first order.
804
 
 
805
 
        This returns all entries for a directory before returning
806
 
        the entries for children of a directory. This is not
807
 
        lexicographically sorted order, and is a hybrid between
808
 
        depth-first and breadth-first.
809
 
 
810
 
        :param yield_parents: If True, yield the parents from the root leading
811
 
            down to specific_file_ids that have been requested. This has no
812
 
            impact if specific_file_ids is None.
813
 
        :return: This yields (path, entry) pairs
814
 
        """
815
 
        if specific_file_ids and not isinstance(specific_file_ids, set):
816
 
            specific_file_ids = set(specific_file_ids)
817
 
        # TODO? Perhaps this should return the from_dir so that the root is
818
 
        # yielded? or maybe an option?
819
 
        if from_dir is None:
820
 
            if self.root is None:
821
 
                return
822
 
            # Optimize a common case
823
 
            if (not yield_parents and specific_file_ids is not None and
824
 
                len(specific_file_ids) == 1):
825
 
                file_id = list(specific_file_ids)[0]
826
 
                if file_id in self:
827
 
                    yield self.id2path(file_id), self[file_id]
828
 
                return
829
 
            from_dir = self.root
830
 
            if (specific_file_ids is None or yield_parents or
831
 
                self.root.file_id in specific_file_ids):
832
 
                yield u'', self.root
833
 
        elif isinstance(from_dir, basestring):
834
 
            from_dir = self[from_dir]
835
 
 
836
 
        if specific_file_ids is not None:
837
 
            # TODO: jam 20070302 This could really be done as a loop rather
838
 
            #       than a bunch of recursive calls.
839
 
            parents = set()
840
 
            byid = self
841
 
            def add_ancestors(file_id):
842
 
                if file_id not in byid:
843
 
                    return
844
 
                parent_id = byid[file_id].parent_id
845
 
                if parent_id is None:
846
 
                    return
847
 
                if parent_id not in parents:
848
 
                    parents.add(parent_id)
849
 
                    add_ancestors(parent_id)
850
 
            for file_id in specific_file_ids:
851
 
                add_ancestors(file_id)
852
 
        else:
853
 
            parents = None
854
 
 
855
 
        stack = [(u'', from_dir)]
856
 
        while stack:
857
 
            cur_relpath, cur_dir = stack.pop()
858
 
 
859
 
            child_dirs = []
860
 
            for child_name, child_ie in sorted(cur_dir.children.iteritems()):
861
 
 
862
 
                child_relpath = cur_relpath + child_name
863
 
 
864
 
                if (specific_file_ids is None or
865
 
                    child_ie.file_id in specific_file_ids or
866
 
                    (yield_parents and child_ie.file_id in parents)):
867
 
                    yield child_relpath, child_ie
868
 
 
869
 
                if child_ie.kind == 'directory':
870
 
                    if parents is None or child_ie.file_id in parents:
871
 
                        child_dirs.append((child_relpath+'/', child_ie))
872
 
            stack.extend(reversed(child_dirs))
873
 
 
874
 
    def _make_delta(self, old):
875
 
        """Make an inventory delta from two inventories."""
876
 
        old_ids = set(old)
877
 
        new_ids = set(self)
878
 
        adds = new_ids - old_ids
879
 
        deletes = old_ids - new_ids
880
 
        common = old_ids.intersection(new_ids)
881
 
        delta = []
882
 
        for file_id in deletes:
883
 
            delta.append((old.id2path(file_id), None, file_id, None))
884
 
        for file_id in adds:
885
 
            delta.append((None, self.id2path(file_id), file_id, self[file_id]))
886
 
        for file_id in common:
887
 
            if old[file_id] != self[file_id]:
888
 
                delta.append((old.id2path(file_id), self.id2path(file_id),
889
 
                    file_id, self[file_id]))
890
 
        return delta
891
 
 
892
 
    def _get_mutable_inventory(self):
893
 
        """Returns a mutable copy of the object.
894
 
 
895
 
        Some inventories are immutable, yet working trees, for example, needs
896
 
        to mutate exisiting inventories instead of creating a new one.
897
 
        """
898
 
        raise NotImplementedError(self._get_mutable_inventory)
899
 
 
900
 
    def make_entry(self, kind, name, parent_id, file_id=None):
901
 
        """Simple thunk to bzrlib.inventory.make_entry."""
902
 
        return make_entry(kind, name, parent_id, file_id)
903
 
 
904
 
    def entries(self):
905
 
        """Return list of (path, ie) for all entries except the root.
906
 
 
907
 
        This may be faster than iter_entries.
908
 
        """
909
 
        accum = []
910
 
        def descend(dir_ie, dir_path):
911
 
            kids = dir_ie.children.items()
912
 
            kids.sort()
913
 
            for name, ie in kids:
914
 
                child_path = osutils.pathjoin(dir_path, name)
915
 
                accum.append((child_path, ie))
916
 
                if ie.kind == 'directory':
917
 
                    descend(ie, child_path)
918
 
 
919
 
        descend(self.root, u'')
920
 
        return accum
921
 
 
922
 
    def directories(self):
923
 
        """Return (path, entry) pairs for all directories, including the root.
924
 
        """
925
 
        accum = []
926
 
        def descend(parent_ie, parent_path):
927
 
            accum.append((parent_path, parent_ie))
928
 
 
929
 
            kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
930
 
            kids.sort()
931
 
 
932
 
            for name, child_ie in kids:
933
 
                child_path = osutils.pathjoin(parent_path, name)
934
 
                descend(child_ie, child_path)
935
 
        descend(self.root, u'')
936
 
        return accum
937
 
 
938
 
    def path2id(self, name):
939
 
        """Walk down through directories to return entry of last component.
940
 
 
941
 
        names may be either a list of path components, or a single
942
 
        string, in which case it is automatically split.
943
 
 
944
 
        This returns the entry of the last component in the path,
945
 
        which may be either a file or a directory.
946
 
 
947
 
        Returns None IFF the path is not found.
948
 
        """
949
 
        if isinstance(name, basestring):
950
 
            name = osutils.splitpath(name)
951
 
 
952
 
        # mutter("lookup path %r" % name)
953
 
 
954
 
        try:
955
 
            parent = self.root
956
 
        except errors.NoSuchId:
957
 
            # root doesn't exist yet so nothing else can
958
 
            return None
959
 
        if parent is None:
960
 
            return None
961
 
        for f in name:
962
 
            try:
963
 
                children = getattr(parent, 'children', None)
964
 
                if children is None:
965
 
                    return None
966
 
                cie = children[f]
967
 
                parent = cie
968
 
            except KeyError:
969
 
                # or raise an error?
970
 
                return None
971
 
 
972
 
        return parent.file_id
973
 
 
974
 
    def filter(self, specific_fileids):
975
 
        """Get an inventory view filtered against a set of file-ids.
976
 
 
977
 
        Children of directories and parents are included.
978
 
 
979
 
        The result may or may not reference the underlying inventory
980
 
        so it should be treated as immutable.
981
 
        """
982
 
        interesting_parents = set()
983
 
        for fileid in specific_fileids:
984
 
            try:
985
 
                interesting_parents.update(self.get_idpath(fileid))
986
 
            except errors.NoSuchId:
987
 
                # This fileid is not in the inventory - that's ok
988
 
                pass
989
 
        entries = self.iter_entries()
990
 
        if self.root is None:
991
 
            return Inventory(root_id=None)
992
 
        other = Inventory(entries.next()[1].file_id)
993
 
        other.root.revision = self.root.revision
994
 
        other.revision_id = self.revision_id
995
 
        directories_to_expand = set()
996
 
        for path, entry in entries:
997
 
            file_id = entry.file_id
998
 
            if (file_id in specific_fileids
999
 
                or entry.parent_id in directories_to_expand):
1000
 
                if entry.kind == 'directory':
1001
 
                    directories_to_expand.add(file_id)
1002
 
            elif file_id not in interesting_parents:
1003
 
                continue
1004
 
            other.add(entry.copy())
1005
 
        return other
1006
 
 
1007
 
    def get_idpath(self, file_id):
1008
 
        """Return a list of file_ids for the path to an entry.
1009
 
 
1010
 
        The list contains one element for each directory followed by
1011
 
        the id of the file itself.  So the length of the returned list
1012
 
        is equal to the depth of the file in the tree, counting the
1013
 
        root directory as depth 1.
1014
 
        """
1015
 
        p = []
1016
 
        for parent in self._iter_file_id_parents(file_id):
1017
 
            p.insert(0, parent.file_id)
1018
 
        return p
1019
 
 
1020
 
 
1021
 
class Inventory(CommonInventory):
 
712
class Inventory(object):
1022
713
    """Inventory of versioned files in a tree.
1023
714
 
1024
715
    This describes which file_id is present at each point in the tree,
1037
728
 
1038
729
    >>> inv = Inventory()
1039
730
    >>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
1040
 
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
 
731
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
1041
732
    >>> inv['123-123'].name
1042
733
    'hello.c'
1043
734
 
1044
735
    May be treated as an iterator or set to look up file ids:
1045
 
 
 
736
    
1046
737
    >>> bool(inv.path2id('hello.c'))
1047
738
    True
1048
739
    >>> '123-123' in inv
1057
748
    Traceback (most recent call last):
1058
749
    BzrError: parent_id {TREE_ROOT} not in inventory
1059
750
    >>> inv.add(InventoryFile('123-123', 'hello.c', 'TREE_ROOT-12345678-12345678'))
1060
 
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None, revision=None)
 
751
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
1061
752
    """
1062
753
    def __init__(self, root_id=ROOT_ID, revision_id=None):
1063
754
        """Create or read an inventory.
1077
768
        self.revision_id = revision_id
1078
769
 
1079
770
    def __repr__(self):
1080
 
        # More than one page of ouput is not useful anymore to debug
1081
 
        max_len = 2048
1082
 
        closing = '...}'
1083
 
        contents = repr(self._byid)
1084
 
        if len(contents) > max_len:
1085
 
            contents = contents[:(max_len-len(closing))] + closing
1086
 
        return "<Inventory object at %x, contents=%r>" % (id(self), contents)
 
771
        return "<Inventory object at %x, contents=%r>" % (id(self), self._byid)
1087
772
 
1088
773
    def apply_delta(self, delta):
1089
774
        """Apply a delta to this inventory.
1090
775
 
1091
 
        See the inventory developers documentation for the theory behind
1092
 
        inventory deltas.
1093
 
 
1094
776
        :param delta: A list of changes to apply. After all the changes are
1095
777
            applied the final inventory must be internally consistent, but it
1096
778
            is ok to supply changes which, if only half-applied would have an
1100
782
 
1101
783
            Each change is a tuple, of the form (old_path, new_path, file_id,
1102
784
            new_entry).
1103
 
 
 
785
            
1104
786
            When new_path is None, the change indicates the removal of an entry
1105
787
            from the inventory and new_entry will be ignored (using None is
1106
788
            appropriate). If new_path is not None, then new_entry must be an
1107
789
            InventoryEntry instance, which will be incorporated into the
1108
790
            inventory (and replace any existing entry with the same file id).
1109
 
 
 
791
            
1110
792
            When old_path is None, the change indicates the addition of
1111
793
            a new entry to the inventory.
1112
 
 
 
794
            
1113
795
            When neither new_path nor old_path are None, the change is a
1114
796
            modification to an entry, such as a rename, reparent, kind change
1115
 
            etc.
 
797
            etc. 
1116
798
 
1117
799
            The children attribute of new_entry is ignored. This is because
1118
800
            this method preserves children automatically across alterations to
1121
803
            change regardless. E.g. in the recursive deletion of a directory -
1122
804
            the directory's children must be included in the delta, or the
1123
805
            final inventory will be invalid.
1124
 
 
1125
 
            Note that a file_id must only appear once within a given delta.
1126
 
            An AssertionError is raised otherwise.
1127
806
        """
1128
 
        # Check that the delta is legal. It would be nice if this could be
1129
 
        # done within the loops below but it's safer to validate the delta
1130
 
        # before starting to mutate the inventory.
1131
 
        unique_file_ids = set([f for _, _, f, _ in delta])
1132
 
        if len(unique_file_ids) != len(delta):
1133
 
            raise AssertionError("a file-id appears multiple times in %r"
1134
 
                    % (delta,))
1135
 
        del unique_file_ids
1136
 
 
1137
807
        children = {}
1138
808
        # Remove all affected items which were in the original inventory,
1139
809
        # starting with the longest paths, thus ensuring parents are examined
1162
832
                # Pop the child which to allow detection of children whose
1163
833
                # parents were deleted and which were not reattached to a new
1164
834
                # parent.
1165
 
                replacement = InventoryDirectory(new_entry.file_id,
1166
 
                    new_entry.name, new_entry.parent_id)
1167
 
                replacement.revision = new_entry.revision
1168
 
                replacement.children = children.pop(replacement.file_id, {})
1169
 
                new_entry = replacement
 
835
                new_entry.children = children.pop(new_entry.file_id, {})
1170
836
            self.add(new_entry)
1171
837
        if len(children):
1172
838
            # Get the parent id that was deleted
1191
857
            other.add(entry.copy())
1192
858
        return other
1193
859
 
1194
 
    def _get_mutable_inventory(self):
1195
 
        """See CommonInventory._get_mutable_inventory."""
1196
 
        return copy.deepcopy(self)
1197
 
 
1198
860
    def __iter__(self):
1199
 
        """Iterate over all file-ids."""
1200
861
        return iter(self._byid)
1201
862
 
1202
 
    def iter_just_entries(self):
1203
 
        """Iterate over all entries.
1204
 
        
1205
 
        Unlike iter_entries(), just the entries are returned (not (path, ie))
1206
 
        and the order of entries is undefined.
1207
 
 
1208
 
        XXX: We may not want to merge this into bzr.dev.
1209
 
        """
1210
 
        if self.root is None:
1211
 
            return
1212
 
        for _, ie in self._byid.iteritems():
1213
 
            yield ie
1214
 
 
1215
863
    def __len__(self):
1216
864
        """Returns number of entries."""
1217
865
        return len(self._byid)
1218
866
 
 
867
    def iter_entries(self, from_dir=None):
 
868
        """Return (path, entry) pairs, in order by name."""
 
869
        if from_dir is None:
 
870
            if self.root is None:
 
871
                return
 
872
            from_dir = self.root
 
873
            yield '', self.root
 
874
        elif isinstance(from_dir, basestring):
 
875
            from_dir = self._byid[from_dir]
 
876
            
 
877
        # unrolling the recursive called changed the time from
 
878
        # 440ms/663ms (inline/total) to 116ms/116ms
 
879
        children = from_dir.children.items()
 
880
        children.sort()
 
881
        children = collections.deque(children)
 
882
        stack = [(u'', children)]
 
883
        while stack:
 
884
            from_dir_relpath, children = stack[-1]
 
885
 
 
886
            while children:
 
887
                name, ie = children.popleft()
 
888
 
 
889
                # we know that from_dir_relpath never ends in a slash
 
890
                # and 'f' doesn't begin with one, we can do a string op, rather
 
891
                # than the checks of pathjoin(), though this means that all paths
 
892
                # start with a slash
 
893
                path = from_dir_relpath + '/' + name
 
894
 
 
895
                yield path[1:], ie
 
896
 
 
897
                if ie.kind != 'directory':
 
898
                    continue
 
899
 
 
900
                # But do this child first
 
901
                new_children = ie.children.items()
 
902
                new_children.sort()
 
903
                new_children = collections.deque(new_children)
 
904
                stack.append((path, new_children))
 
905
                # Break out of inner loop, so that we start outer loop with child
 
906
                break
 
907
            else:
 
908
                # if we finished all children, pop it off the stack
 
909
                stack.pop()
 
910
 
 
911
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
 
912
        yield_parents=False):
 
913
        """Iterate over the entries in a directory first order.
 
914
 
 
915
        This returns all entries for a directory before returning
 
916
        the entries for children of a directory. This is not
 
917
        lexicographically sorted order, and is a hybrid between
 
918
        depth-first and breadth-first.
 
919
 
 
920
        :param yield_parents: If True, yield the parents from the root leading
 
921
            down to specific_file_ids that have been requested. This has no
 
922
            impact if specific_file_ids is None.
 
923
        :return: This yields (path, entry) pairs
 
924
        """
 
925
        if specific_file_ids and not isinstance(specific_file_ids, set):
 
926
            specific_file_ids = set(specific_file_ids)
 
927
        # TODO? Perhaps this should return the from_dir so that the root is
 
928
        # yielded? or maybe an option?
 
929
        if from_dir is None:
 
930
            if self.root is None:
 
931
                return
 
932
            # Optimize a common case
 
933
            if (not yield_parents and specific_file_ids is not None and
 
934
                len(specific_file_ids) == 1):
 
935
                file_id = list(specific_file_ids)[0]
 
936
                if file_id in self:
 
937
                    yield self.id2path(file_id), self[file_id]
 
938
                return 
 
939
            from_dir = self.root
 
940
            if (specific_file_ids is None or yield_parents or
 
941
                self.root.file_id in specific_file_ids):
 
942
                yield u'', self.root
 
943
        elif isinstance(from_dir, basestring):
 
944
            from_dir = self._byid[from_dir]
 
945
 
 
946
        if specific_file_ids is not None:
 
947
            # TODO: jam 20070302 This could really be done as a loop rather
 
948
            #       than a bunch of recursive calls.
 
949
            parents = set()
 
950
            byid = self._byid
 
951
            def add_ancestors(file_id):
 
952
                if file_id not in byid:
 
953
                    return
 
954
                parent_id = byid[file_id].parent_id
 
955
                if parent_id is None:
 
956
                    return
 
957
                if parent_id not in parents:
 
958
                    parents.add(parent_id)
 
959
                    add_ancestors(parent_id)
 
960
            for file_id in specific_file_ids:
 
961
                add_ancestors(file_id)
 
962
        else:
 
963
            parents = None
 
964
            
 
965
        stack = [(u'', from_dir)]
 
966
        while stack:
 
967
            cur_relpath, cur_dir = stack.pop()
 
968
 
 
969
            child_dirs = []
 
970
            for child_name, child_ie in sorted(cur_dir.children.iteritems()):
 
971
 
 
972
                child_relpath = cur_relpath + child_name
 
973
 
 
974
                if (specific_file_ids is None or 
 
975
                    child_ie.file_id in specific_file_ids or
 
976
                    (yield_parents and child_ie.file_id in parents)):
 
977
                    yield child_relpath, child_ie
 
978
 
 
979
                if child_ie.kind == 'directory':
 
980
                    if parents is None or child_ie.file_id in parents:
 
981
                        child_dirs.append((child_relpath+'/', child_ie))
 
982
            stack.extend(reversed(child_dirs))
 
983
 
 
984
    def make_entry(self, kind, name, parent_id, file_id=None):
 
985
        """Simple thunk to bzrlib.inventory.make_entry."""
 
986
        return make_entry(kind, name, parent_id, file_id)
 
987
 
 
988
    def entries(self):
 
989
        """Return list of (path, ie) for all entries except the root.
 
990
 
 
991
        This may be faster than iter_entries.
 
992
        """
 
993
        accum = []
 
994
        def descend(dir_ie, dir_path):
 
995
            kids = dir_ie.children.items()
 
996
            kids.sort()
 
997
            for name, ie in kids:
 
998
                child_path = osutils.pathjoin(dir_path, name)
 
999
                accum.append((child_path, ie))
 
1000
                if ie.kind == 'directory':
 
1001
                    descend(ie, child_path)
 
1002
 
 
1003
        descend(self.root, u'')
 
1004
        return accum
 
1005
 
 
1006
    def directories(self):
 
1007
        """Return (path, entry) pairs for all directories, including the root.
 
1008
        """
 
1009
        accum = []
 
1010
        def descend(parent_ie, parent_path):
 
1011
            accum.append((parent_path, parent_ie))
 
1012
            
 
1013
            kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
 
1014
            kids.sort()
 
1015
 
 
1016
            for name, child_ie in kids:
 
1017
                child_path = osutils.pathjoin(parent_path, name)
 
1018
                descend(child_ie, child_path)
 
1019
        descend(self.root, u'')
 
1020
        return accum
 
1021
        
 
1022
    def __contains__(self, file_id):
 
1023
        """True if this entry contains a file with given id.
 
1024
 
 
1025
        >>> inv = Inventory()
 
1026
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
 
1027
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None)
 
1028
        >>> '123' in inv
 
1029
        True
 
1030
        >>> '456' in inv
 
1031
        False
 
1032
        """
 
1033
        return (file_id in self._byid)
 
1034
 
1219
1035
    def __getitem__(self, file_id):
1220
1036
        """Return the entry for given file_id.
1221
1037
 
1222
1038
        >>> inv = Inventory()
1223
1039
        >>> inv.add(InventoryFile('123123', 'hello.c', ROOT_ID))
1224
 
        InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
 
1040
        InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
1225
1041
        >>> inv['123123'].name
1226
1042
        'hello.c'
1227
1043
        """
1281
1097
        The immediate parent must already be versioned.
1282
1098
 
1283
1099
        Returns the new entry object."""
1284
 
 
 
1100
        
1285
1101
        parts = osutils.splitpath(relpath)
1286
1102
 
1287
1103
        if len(parts) == 0:
1303
1119
 
1304
1120
        >>> inv = Inventory()
1305
1121
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
1306
 
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
 
1122
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None)
1307
1123
        >>> '123' in inv
1308
1124
        True
1309
1125
        >>> del inv['123']
1323
1139
        >>> i1 == i2
1324
1140
        True
1325
1141
        >>> i1.add(InventoryFile('123', 'foo', ROOT_ID))
1326
 
        InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
 
1142
        InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1327
1143
        >>> i1 == i2
1328
1144
        False
1329
1145
        >>> i2.add(InventoryFile('123', 'foo', ROOT_ID))
1330
 
        InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
 
1146
        InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1331
1147
        >>> i1 == i2
1332
1148
        True
1333
1149
        """
1352
1168
            yield ie
1353
1169
            file_id = ie.parent_id
1354
1170
 
1355
 
    def has_filename(self, filename):
1356
 
        return bool(self.path2id(filename))
 
1171
    def get_idpath(self, file_id):
 
1172
        """Return a list of file_ids for the path to an entry.
 
1173
 
 
1174
        The list contains one element for each directory followed by
 
1175
        the id of the file itself.  So the length of the returned list
 
1176
        is equal to the depth of the file in the tree, counting the
 
1177
        root directory as depth 1.
 
1178
        """
 
1179
        p = []
 
1180
        for parent in self._iter_file_id_parents(file_id):
 
1181
            p.insert(0, parent.file_id)
 
1182
        return p
 
1183
 
 
1184
    def id2path(self, file_id):
 
1185
        """Return as a string the path to file_id.
 
1186
        
 
1187
        >>> i = Inventory()
 
1188
        >>> e = i.add(InventoryDirectory('src-id', 'src', ROOT_ID))
 
1189
        >>> e = i.add(InventoryFile('foo-id', 'foo.c', parent_id='src-id'))
 
1190
        >>> print i.id2path('foo-id')
 
1191
        src/foo.c
 
1192
        """
 
1193
        # get all names, skipping root
 
1194
        return '/'.join(reversed(
 
1195
            [parent.name for parent in 
 
1196
             self._iter_file_id_parents(file_id)][:-1]))
 
1197
            
 
1198
    def path2id(self, name):
 
1199
        """Walk down through directories to return entry of last component.
 
1200
 
 
1201
        names may be either a list of path components, or a single
 
1202
        string, in which case it is automatically split.
 
1203
 
 
1204
        This returns the entry of the last component in the path,
 
1205
        which may be either a file or a directory.
 
1206
 
 
1207
        Returns None IFF the path is not found.
 
1208
        """
 
1209
        if isinstance(name, basestring):
 
1210
            name = osutils.splitpath(name)
 
1211
 
 
1212
        # mutter("lookup path %r" % name)
 
1213
 
 
1214
        parent = self.root
 
1215
        if parent is None:
 
1216
            return None
 
1217
        for f in name:
 
1218
            try:
 
1219
                children = getattr(parent, 'children', None)
 
1220
                if children is None:
 
1221
                    return None
 
1222
                cie = children[f]
 
1223
                parent = cie
 
1224
            except KeyError:
 
1225
                # or raise an error?
 
1226
                return None
 
1227
 
 
1228
        return parent.file_id
 
1229
 
 
1230
    def has_filename(self, names):
 
1231
        return bool(self.path2id(names))
1357
1232
 
1358
1233
    def has_id(self, file_id):
1359
1234
        return (file_id in self._byid)
1360
1235
 
1361
 
    def _make_delta(self, old):
1362
 
        """Make an inventory delta from two inventories."""
1363
 
        old_getter = getattr(old, '_byid', old)
1364
 
        new_getter = self._byid
1365
 
        old_ids = set(old_getter)
1366
 
        new_ids = set(new_getter)
1367
 
        adds = new_ids - old_ids
1368
 
        deletes = old_ids - new_ids
1369
 
        if not adds and not deletes:
1370
 
            common = new_ids
1371
 
        else:
1372
 
            common = old_ids.intersection(new_ids)
1373
 
        delta = []
1374
 
        for file_id in deletes:
1375
 
            delta.append((old.id2path(file_id), None, file_id, None))
1376
 
        for file_id in adds:
1377
 
            delta.append((None, self.id2path(file_id), file_id, self[file_id]))
1378
 
        for file_id in common:
1379
 
            new_ie = new_getter[file_id]
1380
 
            old_ie = old_getter[file_id]
1381
 
            # If xml_serializer returns the cached InventoryEntries (rather
1382
 
            # than always doing .copy()), inlining the 'is' check saves 2.7M
1383
 
            # calls to __eq__.  Under lsprof this saves 20s => 6s.
1384
 
            # It is a minor improvement without lsprof.
1385
 
            if old_ie is new_ie or old_ie == new_ie:
1386
 
                continue
1387
 
            else:
1388
 
                delta.append((old.id2path(file_id), self.id2path(file_id),
1389
 
                              file_id, new_ie))
1390
 
        return delta
1391
 
 
1392
1236
    def remove_recursive_id(self, file_id):
1393
1237
        """Remove file_id, and children, from the inventory.
1394
 
 
 
1238
        
1395
1239
        :param file_id: A file_id to remove.
1396
1240
        """
1397
1241
        to_find_delete = [self._byid[file_id]]
1436
1280
 
1437
1281
        del old_parent.children[file_ie.name]
1438
1282
        new_parent.children[new_name] = file_ie
1439
 
 
 
1283
        
1440
1284
        file_ie.name = new_name
1441
1285
        file_ie.parent_id = new_parent_id
1442
1286
 
1444
1288
        return self.root is not None and file_id == self.root.file_id
1445
1289
 
1446
1290
 
1447
 
class CHKInventory(CommonInventory):
1448
 
    """An inventory persisted in a CHK store.
1449
 
 
1450
 
    By design, a CHKInventory is immutable so many of the methods
1451
 
    supported by Inventory - add, rename, apply_delta, etc - are *not*
1452
 
    supported. To create a new CHKInventory, use create_by_apply_delta()
1453
 
    or from_inventory(), say.
1454
 
 
1455
 
    Internally, a CHKInventory has one or two CHKMaps:
1456
 
 
1457
 
    * id_to_entry - a map from (file_id,) => InventoryEntry as bytes
1458
 
    * parent_id_basename_to_file_id - a map from (parent_id, basename_utf8)
1459
 
        => file_id as bytes
1460
 
 
1461
 
    The second map is optional and not present in early CHkRepository's.
1462
 
 
1463
 
    No caching is performed: every method call or item access will perform
1464
 
    requests to the storage layer. As such, keep references to objects you
1465
 
    want to reuse.
1466
 
    """
1467
 
 
1468
 
    def __init__(self, search_key_name):
1469
 
        CommonInventory.__init__(self)
1470
 
        self._fileid_to_entry_cache = {}
1471
 
        self._path_to_fileid_cache = {}
1472
 
        self._search_key_name = search_key_name
1473
 
 
1474
 
    def __eq__(self, other):
1475
 
        """Compare two sets by comparing their contents."""
1476
 
        if not isinstance(other, CHKInventory):
1477
 
            return NotImplemented
1478
 
 
1479
 
        this_key = self.id_to_entry.key()
1480
 
        other_key = other.id_to_entry.key()
1481
 
        this_pid_key = self.parent_id_basename_to_file_id.key()
1482
 
        other_pid_key = other.parent_id_basename_to_file_id.key()
1483
 
        if None in (this_key, this_pid_key, other_key, other_pid_key):
1484
 
            return False
1485
 
        return this_key == other_key and this_pid_key == other_pid_key
1486
 
 
1487
 
    def _entry_to_bytes(self, entry):
1488
 
        """Serialise entry as a single bytestring.
1489
 
 
1490
 
        :param Entry: An inventory entry.
1491
 
        :return: A bytestring for the entry.
1492
 
 
1493
 
        The BNF:
1494
 
        ENTRY ::= FILE | DIR | SYMLINK | TREE
1495
 
        FILE ::= "file: " COMMON SEP SHA SEP SIZE SEP EXECUTABLE
1496
 
        DIR ::= "dir: " COMMON
1497
 
        SYMLINK ::= "symlink: " COMMON SEP TARGET_UTF8
1498
 
        TREE ::= "tree: " COMMON REFERENCE_REVISION
1499
 
        COMMON ::= FILE_ID SEP PARENT_ID SEP NAME_UTF8 SEP REVISION
1500
 
        SEP ::= "\n"
1501
 
        """
1502
 
        if entry.parent_id is not None:
1503
 
            parent_str = entry.parent_id
1504
 
        else:
1505
 
            parent_str = ''
1506
 
        name_str = entry.name.encode("utf8")
1507
 
        if entry.kind == 'file':
1508
 
            if entry.executable:
1509
 
                exec_str = "Y"
1510
 
            else:
1511
 
                exec_str = "N"
1512
 
            return "file: %s\n%s\n%s\n%s\n%s\n%d\n%s" % (
1513
 
                entry.file_id, parent_str, name_str, entry.revision,
1514
 
                entry.text_sha1, entry.text_size, exec_str)
1515
 
        elif entry.kind == 'directory':
1516
 
            return "dir: %s\n%s\n%s\n%s" % (
1517
 
                entry.file_id, parent_str, name_str, entry.revision)
1518
 
        elif entry.kind == 'symlink':
1519
 
            return "symlink: %s\n%s\n%s\n%s\n%s" % (
1520
 
                entry.file_id, parent_str, name_str, entry.revision,
1521
 
                entry.symlink_target.encode("utf8"))
1522
 
        elif entry.kind == 'tree-reference':
1523
 
            return "tree: %s\n%s\n%s\n%s\n%s" % (
1524
 
                entry.file_id, parent_str, name_str, entry.revision,
1525
 
                entry.reference_revision)
1526
 
        else:
1527
 
            raise ValueError("unknown kind %r" % entry.kind)
1528
 
 
1529
 
    @staticmethod
1530
 
    def _bytes_to_utf8name_key(bytes):
1531
 
        """Get the file_id, revision_id key out of bytes."""
1532
 
        # We don't normally care about name, except for times when we want
1533
 
        # to filter out empty names because of non rich-root...
1534
 
        sections = bytes.split('\n')
1535
 
        kind, file_id = sections[0].split(': ')
1536
 
        return (sections[2], file_id, sections[3])
1537
 
 
1538
 
    def _bytes_to_entry(self, bytes):
1539
 
        """Deserialise a serialised entry."""
1540
 
        sections = bytes.split('\n')
1541
 
        if sections[0].startswith("file: "):
1542
 
            result = InventoryFile(sections[0][6:],
1543
 
                sections[2].decode('utf8'),
1544
 
                sections[1])
1545
 
            result.text_sha1 = sections[4]
1546
 
            result.text_size = int(sections[5])
1547
 
            result.executable = sections[6] == "Y"
1548
 
        elif sections[0].startswith("dir: "):
1549
 
            result = CHKInventoryDirectory(sections[0][5:],
1550
 
                sections[2].decode('utf8'),
1551
 
                sections[1], self)
1552
 
        elif sections[0].startswith("symlink: "):
1553
 
            result = InventoryLink(sections[0][9:],
1554
 
                sections[2].decode('utf8'),
1555
 
                sections[1])
1556
 
            result.symlink_target = sections[4].decode('utf8')
1557
 
        elif sections[0].startswith("tree: "):
1558
 
            result = TreeReference(sections[0][6:],
1559
 
                sections[2].decode('utf8'),
1560
 
                sections[1])
1561
 
            result.reference_revision = sections[4]
1562
 
        else:
1563
 
            raise ValueError("Not a serialised entry %r" % bytes)
1564
 
        result.revision = sections[3]
1565
 
        if result.parent_id == '':
1566
 
            result.parent_id = None
1567
 
        self._fileid_to_entry_cache[result.file_id] = result
1568
 
        return result
1569
 
 
1570
 
    def _get_mutable_inventory(self):
1571
 
        """See CommonInventory._get_mutable_inventory."""
1572
 
        entries = self.iter_entries()
1573
 
        inv = Inventory(None, self.revision_id)
1574
 
        for path, inv_entry in entries:
1575
 
            inv.add(inv_entry.copy())
1576
 
        return inv
1577
 
 
1578
 
    def create_by_apply_delta(self, inventory_delta, new_revision_id,
1579
 
        propagate_caches=False):
1580
 
        """Create a new CHKInventory by applying inventory_delta to this one.
1581
 
 
1582
 
        See the inventory developers documentation for the theory behind
1583
 
        inventory deltas.
1584
 
 
1585
 
        :param inventory_delta: The inventory delta to apply. See
1586
 
            Inventory.apply_delta for details.
1587
 
        :param new_revision_id: The revision id of the resulting CHKInventory.
1588
 
        :param propagate_caches: If True, the caches for this inventory are
1589
 
          copied to and updated for the result.
1590
 
        :return: The new CHKInventory.
1591
 
        """
1592
 
        result = CHKInventory(self._search_key_name)
1593
 
        if propagate_caches:
1594
 
            # Just propagate the path-to-fileid cache for now
1595
 
            result._path_to_fileid_cache = dict(self._path_to_fileid_cache.iteritems())
1596
 
        search_key_func = chk_map.search_key_registry.get(self._search_key_name)
1597
 
        self.id_to_entry._ensure_root()
1598
 
        maximum_size = self.id_to_entry._root_node.maximum_size
1599
 
        result.revision_id = new_revision_id
1600
 
        result.id_to_entry = chk_map.CHKMap(
1601
 
            self.id_to_entry._store,
1602
 
            self.id_to_entry.key(),
1603
 
            search_key_func=search_key_func)
1604
 
        result.id_to_entry._ensure_root()
1605
 
        result.id_to_entry._root_node.set_maximum_size(maximum_size)
1606
 
        parent_id_basename_delta = []
1607
 
        if self.parent_id_basename_to_file_id is not None:
1608
 
            result.parent_id_basename_to_file_id = chk_map.CHKMap(
1609
 
                self.parent_id_basename_to_file_id._store,
1610
 
                self.parent_id_basename_to_file_id.key(),
1611
 
                search_key_func=search_key_func)
1612
 
            result.parent_id_basename_to_file_id._ensure_root()
1613
 
            self.parent_id_basename_to_file_id._ensure_root()
1614
 
            result_p_id_root = result.parent_id_basename_to_file_id._root_node
1615
 
            p_id_root = self.parent_id_basename_to_file_id._root_node
1616
 
            result_p_id_root.set_maximum_size(p_id_root.maximum_size)
1617
 
            result_p_id_root._key_width = p_id_root._key_width
1618
 
        else:
1619
 
            result.parent_id_basename_to_file_id = None
1620
 
        result.root_id = self.root_id
1621
 
        id_to_entry_delta = []
1622
 
        for old_path, new_path, file_id, entry in inventory_delta:
1623
 
            # file id changes
1624
 
            if new_path == '':
1625
 
                result.root_id = file_id
1626
 
            if new_path is None:
1627
 
                # Make a delete:
1628
 
                new_key = None
1629
 
                new_value = None
1630
 
                # Update caches
1631
 
                if propagate_caches:
1632
 
                    try:
1633
 
                        del result._path_to_fileid_cache[old_path]
1634
 
                    except KeyError:
1635
 
                        pass
1636
 
            else:
1637
 
                new_key = (file_id,)
1638
 
                new_value = result._entry_to_bytes(entry)
1639
 
                # Update caches. It's worth doing this whether
1640
 
                # we're propagating the old caches or not.
1641
 
                result._path_to_fileid_cache[new_path] = file_id
1642
 
            if old_path is None:
1643
 
                old_key = None
1644
 
            else:
1645
 
                old_key = (file_id,)
1646
 
            id_to_entry_delta.append((old_key, new_key, new_value))
1647
 
            if result.parent_id_basename_to_file_id is not None:
1648
 
                # parent_id, basename changes
1649
 
                if old_path is None:
1650
 
                    old_key = None
1651
 
                else:
1652
 
                    old_entry = self[file_id]
1653
 
                    old_key = self._parent_id_basename_key(old_entry)
1654
 
                if new_path is None:
1655
 
                    new_key = None
1656
 
                    new_value = None
1657
 
                else:
1658
 
                    new_key = self._parent_id_basename_key(entry)
1659
 
                    new_value = file_id
1660
 
                if old_key != new_key:
1661
 
                    # If the two keys are the same, the value will be unchanged
1662
 
                    # as its always the file id.
1663
 
                    parent_id_basename_delta.append((old_key, new_key, new_value))
1664
 
        result.id_to_entry.apply_delta(id_to_entry_delta)
1665
 
        if parent_id_basename_delta:
1666
 
            result.parent_id_basename_to_file_id.apply_delta(parent_id_basename_delta)
1667
 
        return result
1668
 
 
1669
 
    @classmethod
1670
 
    def deserialise(klass, chk_store, bytes, expected_revision_id):
1671
 
        """Deserialise a CHKInventory.
1672
 
 
1673
 
        :param chk_store: A CHK capable VersionedFiles instance.
1674
 
        :param bytes: The serialised bytes.
1675
 
        :param expected_revision_id: The revision ID we think this inventory is
1676
 
            for.
1677
 
        :return: A CHKInventory
1678
 
        """
1679
 
        lines = bytes.split('\n')
1680
 
        if lines[-1] != '':
1681
 
            raise AssertionError('bytes to deserialize must end with an eol')
1682
 
        lines.pop()
1683
 
        if lines[0] != 'chkinventory:':
1684
 
            raise ValueError("not a serialised CHKInventory: %r" % bytes)
1685
 
        info = {}
1686
 
        allowed_keys = frozenset(['root_id', 'revision_id', 'search_key_name',
1687
 
                                  'parent_id_basename_to_file_id',
1688
 
                                  'id_to_entry'])
1689
 
        for line in lines[1:]:
1690
 
            key, value = line.split(': ', 1)
1691
 
            if key not in allowed_keys:
1692
 
                raise errors.BzrError('Unknown key in inventory: %r\n%r'
1693
 
                                      % (key, bytes))
1694
 
            if key in info:
1695
 
                raise errors.BzrError('Duplicate key in inventory: %r\n%r'
1696
 
                                      % (key, bytes))
1697
 
            info[key] = value
1698
 
        revision_id = info['revision_id']
1699
 
        root_id = info['root_id']
1700
 
        search_key_name = info.get('search_key_name', 'plain')
1701
 
        parent_id_basename_to_file_id = info.get(
1702
 
            'parent_id_basename_to_file_id', None)
1703
 
        id_to_entry = info['id_to_entry']
1704
 
 
1705
 
        result = CHKInventory(search_key_name)
1706
 
        result.revision_id = revision_id
1707
 
        result.root_id = root_id
1708
 
        search_key_func = chk_map.search_key_registry.get(
1709
 
                            result._search_key_name)
1710
 
        if parent_id_basename_to_file_id is not None:
1711
 
            result.parent_id_basename_to_file_id = chk_map.CHKMap(
1712
 
                chk_store, (parent_id_basename_to_file_id,),
1713
 
                search_key_func=search_key_func)
1714
 
        else:
1715
 
            result.parent_id_basename_to_file_id = None
1716
 
 
1717
 
        result.id_to_entry = chk_map.CHKMap(chk_store, (id_to_entry,),
1718
 
                                            search_key_func=search_key_func)
1719
 
        if (result.revision_id,) != expected_revision_id:
1720
 
            raise ValueError("Mismatched revision id and expected: %r, %r" %
1721
 
                (result.revision_id, expected_revision_id))
1722
 
        return result
1723
 
 
1724
 
    @classmethod
1725
 
    def from_inventory(klass, chk_store, inventory, maximum_size=0, search_key_name='plain'):
1726
 
        """Create a CHKInventory from an existing inventory.
1727
 
 
1728
 
        The content of inventory is copied into the chk_store, and a
1729
 
        CHKInventory referencing that is returned.
1730
 
 
1731
 
        :param chk_store: A CHK capable VersionedFiles instance.
1732
 
        :param inventory: The inventory to copy.
1733
 
        :param maximum_size: The CHKMap node size limit.
1734
 
        :param search_key_name: The identifier for the search key function
1735
 
        """
1736
 
        result = klass(search_key_name)
1737
 
        result.revision_id = inventory.revision_id
1738
 
        result.root_id = inventory.root.file_id
1739
 
 
1740
 
        entry_to_bytes = result._entry_to_bytes
1741
 
        parent_id_basename_key = result._parent_id_basename_key
1742
 
        id_to_entry_dict = {}
1743
 
        parent_id_basename_dict = {}
1744
 
        for path, entry in inventory.iter_entries():
1745
 
            id_to_entry_dict[(entry.file_id,)] = entry_to_bytes(entry)
1746
 
            p_id_key = parent_id_basename_key(entry)
1747
 
            parent_id_basename_dict[p_id_key] = entry.file_id
1748
 
 
1749
 
        result._populate_from_dicts(chk_store, id_to_entry_dict,
1750
 
            parent_id_basename_dict, maximum_size=maximum_size)
1751
 
        return result
1752
 
 
1753
 
    def _populate_from_dicts(self, chk_store, id_to_entry_dict,
1754
 
                             parent_id_basename_dict, maximum_size):
1755
 
        search_key_func = chk_map.search_key_registry.get(self._search_key_name)
1756
 
        root_key = chk_map.CHKMap.from_dict(chk_store, id_to_entry_dict,
1757
 
                   maximum_size=maximum_size, key_width=1,
1758
 
                   search_key_func=search_key_func)
1759
 
        self.id_to_entry = chk_map.CHKMap(chk_store, root_key,
1760
 
                                          search_key_func)
1761
 
        root_key = chk_map.CHKMap.from_dict(chk_store,
1762
 
                   parent_id_basename_dict,
1763
 
                   maximum_size=maximum_size, key_width=2,
1764
 
                   search_key_func=search_key_func)
1765
 
        self.parent_id_basename_to_file_id = chk_map.CHKMap(chk_store,
1766
 
                                                    root_key, search_key_func)
1767
 
 
1768
 
    def _parent_id_basename_key(self, entry):
1769
 
        """Create a key for a entry in a parent_id_basename_to_file_id index."""
1770
 
        if entry.parent_id is not None:
1771
 
            parent_id = entry.parent_id
1772
 
        else:
1773
 
            parent_id = ''
1774
 
        return parent_id, entry.name.encode('utf8')
1775
 
 
1776
 
    def __getitem__(self, file_id):
1777
 
        """map a single file_id -> InventoryEntry."""
1778
 
        if file_id is None:
1779
 
            raise errors.NoSuchId(self, file_id)
1780
 
        result = self._fileid_to_entry_cache.get(file_id, None)
1781
 
        if result is not None:
1782
 
            return result
1783
 
        try:
1784
 
            return self._bytes_to_entry(
1785
 
                self.id_to_entry.iteritems([(file_id,)]).next()[1])
1786
 
        except StopIteration:
1787
 
            # really we're passing an inventory, not a tree...
1788
 
            raise errors.NoSuchId(self, file_id)
1789
 
 
1790
 
    def has_id(self, file_id):
1791
 
        # Perhaps have an explicit 'contains' method on CHKMap ?
1792
 
        if self._fileid_to_entry_cache.get(file_id, None) is not None:
1793
 
            return True
1794
 
        return len(list(self.id_to_entry.iteritems([(file_id,)]))) == 1
1795
 
 
1796
 
    def is_root(self, file_id):
1797
 
        return file_id == self.root_id
1798
 
 
1799
 
    def _iter_file_id_parents(self, file_id):
1800
 
        """Yield the parents of file_id up to the root."""
1801
 
        while file_id is not None:
1802
 
            try:
1803
 
                ie = self[file_id]
1804
 
            except KeyError:
1805
 
                raise errors.NoSuchId(tree=self, file_id=file_id)
1806
 
            yield ie
1807
 
            file_id = ie.parent_id
1808
 
 
1809
 
    def __iter__(self):
1810
 
        """Iterate over all file-ids."""
1811
 
        for key, _ in self.id_to_entry.iteritems():
1812
 
            yield key[-1]
1813
 
 
1814
 
    def iter_just_entries(self):
1815
 
        """Iterate over all entries.
1816
 
        
1817
 
        Unlike iter_entries(), just the entries are returned (not (path, ie))
1818
 
        and the order of entries is undefined.
1819
 
 
1820
 
        XXX: We may not want to merge this into bzr.dev.
1821
 
        """
1822
 
        for key, entry in self.id_to_entry.iteritems():
1823
 
            file_id = key[0]
1824
 
            ie = self._fileid_to_entry_cache.get(file_id, None)
1825
 
            if ie is None:
1826
 
                ie = self._bytes_to_entry(entry)
1827
 
                self._fileid_to_entry_cache[file_id] = ie
1828
 
            yield ie
1829
 
 
1830
 
    def iter_changes(self, basis):
1831
 
        """Generate a Tree.iter_changes change list between this and basis.
1832
 
 
1833
 
        :param basis: Another CHKInventory.
1834
 
        :return: An iterator over the changes between self and basis, as per
1835
 
            tree.iter_changes().
1836
 
        """
1837
 
        # We want: (file_id, (path_in_source, path_in_target),
1838
 
        # changed_content, versioned, parent, name, kind,
1839
 
        # executable)
1840
 
        for key, basis_value, self_value in \
1841
 
            self.id_to_entry.iter_changes(basis.id_to_entry):
1842
 
            file_id = key[0]
1843
 
            if basis_value is not None:
1844
 
                basis_entry = basis._bytes_to_entry(basis_value)
1845
 
                path_in_source = basis.id2path(file_id)
1846
 
                basis_parent = basis_entry.parent_id
1847
 
                basis_name = basis_entry.name
1848
 
                basis_executable = basis_entry.executable
1849
 
            else:
1850
 
                path_in_source = None
1851
 
                basis_parent = None
1852
 
                basis_name = None
1853
 
                basis_executable = None
1854
 
            if self_value is not None:
1855
 
                self_entry = self._bytes_to_entry(self_value)
1856
 
                path_in_target = self.id2path(file_id)
1857
 
                self_parent = self_entry.parent_id
1858
 
                self_name = self_entry.name
1859
 
                self_executable = self_entry.executable
1860
 
            else:
1861
 
                path_in_target = None
1862
 
                self_parent = None
1863
 
                self_name = None
1864
 
                self_executable = None
1865
 
            if basis_value is None:
1866
 
                # add
1867
 
                kind = (None, self_entry.kind)
1868
 
                versioned = (False, True)
1869
 
            elif self_value is None:
1870
 
                # delete
1871
 
                kind = (basis_entry.kind, None)
1872
 
                versioned = (True, False)
1873
 
            else:
1874
 
                kind = (basis_entry.kind, self_entry.kind)
1875
 
                versioned = (True, True)
1876
 
            changed_content = False
1877
 
            if kind[0] != kind[1]:
1878
 
                changed_content = True
1879
 
            elif kind[0] == 'file':
1880
 
                if (self_entry.text_size != basis_entry.text_size or
1881
 
                    self_entry.text_sha1 != basis_entry.text_sha1):
1882
 
                    changed_content = True
1883
 
            elif kind[0] == 'symlink':
1884
 
                if self_entry.symlink_target != basis_entry.symlink_target:
1885
 
                    changed_content = True
1886
 
            elif kind[0] == 'tree-reference':
1887
 
                if (self_entry.reference_revision !=
1888
 
                    basis_entry.reference_revision):
1889
 
                    changed_content = True
1890
 
            parent = (basis_parent, self_parent)
1891
 
            name = (basis_name, self_name)
1892
 
            executable = (basis_executable, self_executable)
1893
 
            if (not changed_content
1894
 
                and parent[0] == parent[1]
1895
 
                and name[0] == name[1]
1896
 
                and executable[0] == executable[1]):
1897
 
                # Could happen when only the revision changed for a directory
1898
 
                # for instance.
1899
 
                continue
1900
 
            yield (file_id, (path_in_source, path_in_target), changed_content,
1901
 
                versioned, parent, name, kind, executable)
1902
 
 
1903
 
    def __len__(self):
1904
 
        """Return the number of entries in the inventory."""
1905
 
        return len(self.id_to_entry)
1906
 
 
1907
 
    def _make_delta(self, old):
1908
 
        """Make an inventory delta from two inventories."""
1909
 
        if type(old) != CHKInventory:
1910
 
            return CommonInventory._make_delta(self, old)
1911
 
        delta = []
1912
 
        for key, old_value, self_value in \
1913
 
            self.id_to_entry.iter_changes(old.id_to_entry):
1914
 
            file_id = key[0]
1915
 
            if old_value is not None:
1916
 
                old_path = old.id2path(file_id)
1917
 
            else:
1918
 
                old_path = None
1919
 
            if self_value is not None:
1920
 
                entry = self._bytes_to_entry(self_value)
1921
 
                self._fileid_to_entry_cache[file_id] = entry
1922
 
                new_path = self.id2path(file_id)
1923
 
            else:
1924
 
                entry = None
1925
 
                new_path = None
1926
 
            delta.append((old_path, new_path, file_id, entry))
1927
 
        return delta
1928
 
 
1929
 
    def path2id(self, name):
1930
 
        """See CommonInventory.path2id()."""
1931
 
        result = self._path_to_fileid_cache.get(name, None)
1932
 
        if result is None:
1933
 
            result = CommonInventory.path2id(self, name)
1934
 
            self._path_to_fileid_cache[name] = result
1935
 
        return result
1936
 
 
1937
 
    def to_lines(self):
1938
 
        """Serialise the inventory to lines."""
1939
 
        lines = ["chkinventory:\n"]
1940
 
        if self._search_key_name != 'plain':
1941
 
            # custom ordering grouping things that don't change together
1942
 
            lines.append('search_key_name: %s\n' % (self._search_key_name,))
1943
 
            lines.append("root_id: %s\n" % self.root_id)
1944
 
            lines.append('parent_id_basename_to_file_id: %s\n' %
1945
 
                self.parent_id_basename_to_file_id.key())
1946
 
            lines.append("revision_id: %s\n" % self.revision_id)
1947
 
            lines.append("id_to_entry: %s\n" % self.id_to_entry.key())
1948
 
        else:
1949
 
            lines.append("revision_id: %s\n" % self.revision_id)
1950
 
            lines.append("root_id: %s\n" % self.root_id)
1951
 
            if self.parent_id_basename_to_file_id is not None:
1952
 
                lines.append('parent_id_basename_to_file_id: %s\n' %
1953
 
                    self.parent_id_basename_to_file_id.key())
1954
 
            lines.append("id_to_entry: %s\n" % self.id_to_entry.key())
1955
 
        return lines
1956
 
 
1957
 
    @property
1958
 
    def root(self):
1959
 
        """Get the root entry."""
1960
 
        return self[self.root_id]
1961
 
 
1962
 
 
1963
 
class CHKInventoryDirectory(InventoryDirectory):
1964
 
    """A directory in an inventory."""
1965
 
 
1966
 
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
1967
 
                 'text_id', 'parent_id', '_children', 'executable',
1968
 
                 'revision', 'symlink_target', 'reference_revision',
1969
 
                 '_chk_inventory']
1970
 
 
1971
 
    def __init__(self, file_id, name, parent_id, chk_inventory):
1972
 
        # Don't call InventoryDirectory.__init__ - it isn't right for this
1973
 
        # class.
1974
 
        InventoryEntry.__init__(self, file_id, name, parent_id)
1975
 
        self._children = None
1976
 
        self.kind = 'directory'
1977
 
        self._chk_inventory = chk_inventory
1978
 
 
1979
 
    @property
1980
 
    def children(self):
1981
 
        """Access the list of children of this directory.
1982
 
 
1983
 
        With a parent_id_basename_to_file_id index, loads all the children,
1984
 
        without loads the entire index. Without is bad. A more sophisticated
1985
 
        proxy object might be nice, to allow partial loading of children as
1986
 
        well when specific names are accessed. (So path traversal can be
1987
 
        written in the obvious way but not examine siblings.).
1988
 
        """
1989
 
        if self._children is not None:
1990
 
            return self._children
1991
 
        # No longer supported
1992
 
        if self._chk_inventory.parent_id_basename_to_file_id is None:
1993
 
            raise AssertionError("Inventories without"
1994
 
                " parent_id_basename_to_file_id are no longer supported")
1995
 
        result = {}
1996
 
        # XXX: Todo - use proxy objects for the children rather than loading
1997
 
        # all when the attribute is referenced.
1998
 
        parent_id_index = self._chk_inventory.parent_id_basename_to_file_id
1999
 
        child_keys = set()
2000
 
        for (parent_id, name_utf8), file_id in parent_id_index.iteritems(
2001
 
            key_filter=[(self.file_id,)]):
2002
 
            child_keys.add((file_id,))
2003
 
        cached = set()
2004
 
        for file_id_key in child_keys:
2005
 
            entry = self._chk_inventory._fileid_to_entry_cache.get(
2006
 
                file_id_key[0], None)
2007
 
            if entry is not None:
2008
 
                result[entry.name] = entry
2009
 
                cached.add(file_id_key)
2010
 
        child_keys.difference_update(cached)
2011
 
        # populate; todo: do by name
2012
 
        id_to_entry = self._chk_inventory.id_to_entry
2013
 
        for file_id_key, bytes in id_to_entry.iteritems(child_keys):
2014
 
            entry = self._chk_inventory._bytes_to_entry(bytes)
2015
 
            result[entry.name] = entry
2016
 
            self._chk_inventory._fileid_to_entry_cache[file_id_key[0]] = entry
2017
 
        self._children = result
2018
 
        return result
2019
 
 
2020
1291
entry_factory = {
2021
1292
    'directory': InventoryDirectory,
2022
1293
    'file': InventoryFile,
2070
1341
    global _NAME_RE
2071
1342
    if _NAME_RE is None:
2072
1343
        _NAME_RE = re.compile(r'^[^/\\]+$')
2073
 
 
 
1344
        
2074
1345
    return bool(_NAME_RE.match(name))