~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Robert Collins
  • Date: 2005-09-30 02:54:51 UTC
  • mfrom: (1395)
  • mto: This revision was merged to the branch mainline in revision 1397.
  • Revision ID: robertc@robertcollins.net-20050930025451-47b9e412202be44b
symlink and weaves, whaddya know

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
 
18
# TODO: Maybe also keep the full path of the entry, and the children?
 
19
# But those depend on its position within a particular inventory, and
 
20
# it would be nice not to need to hold the backpointer here.
 
21
 
 
22
# TODO: Perhaps split InventoryEntry into subclasses for files,
 
23
# directories, etc etc.
 
24
 
 
25
 
18
26
# This should really be an id randomly assigned when the tree is
19
27
# created, but it's not for now.
20
28
ROOT_ID = "TREE_ROOT"
28
36
import bzrlib
29
37
from bzrlib.errors import BzrError, BzrCheckError
30
38
 
31
 
from bzrlib.osutils import quotefn, splitpath, joinpath, appendpath
 
39
from bzrlib.osutils import quotefn, splitpath, joinpath, appendpath, sha_strings
32
40
from bzrlib.trace import mutter
33
41
from bzrlib.errors import NotVersionedError
34
42
 
39
47
    An InventoryEntry has the following fields, which are also
40
48
    present in the XML inventory-entry element:
41
49
 
42
 
    * *file_id*
43
 
    * *name*: (only the basename within the directory, must not
44
 
      contain slashes)
45
 
    * *kind*: "directory" or "file"
46
 
    * *directory_id*: (if absent/null means the branch root directory)
47
 
    * *text_sha1*: only for files
48
 
    * *text_size*: in bytes, only for files 
49
 
    * *text_id*: identifier for the text version, only for files
50
 
 
51
 
    InventoryEntries can also exist inside a WorkingTree
52
 
    inventory, in which case they are not yet bound to a
53
 
    particular revision of the file.  In that case the text_sha1,
54
 
    text_size and text_id are absent.
55
 
 
 
50
    file_id
 
51
 
 
52
    name
 
53
        (within the parent directory)
 
54
 
 
55
    kind
 
56
        'directory' or 'file'
 
57
 
 
58
    parent_id
 
59
        file_id of the parent directory, or ROOT_ID
 
60
 
 
61
    name_version
 
62
        the revision_id in which the name or parent of this file was
 
63
        last changed
 
64
 
 
65
    text_sha1
 
66
        sha-1 of the text of the file
 
67
        
 
68
    text_size
 
69
        size in bytes of the text of the file
 
70
        
 
71
    text_version
 
72
        the revision_id in which the text of this file was introduced
 
73
 
 
74
    (reading a version 4 tree created a text_id field.)
56
75
 
57
76
    >>> i = Inventory()
58
77
    >>> i.path2id('')
93
112
    src/wibble/wibble.c
94
113
    >>> i.id2path('2326').replace('\\\\', '/')
95
114
    'src/wibble/wibble.c'
96
 
 
97
 
    TODO: Maybe also keep the full path of the entry, and the children?
98
 
           But those depend on its position within a particular inventory, and
99
 
           it would be nice not to need to hold the backpointer here.
100
115
    """
101
 
 
102
 
    # TODO: split InventoryEntry into subclasses for files,
103
 
    # directories, etc etc.
104
 
 
 
116
    
105
117
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
106
118
                 'text_id', 'parent_id', 'children',
107
 
                 'text_version', 'entry_version', 'symlink_target']
 
119
                 'text_version', 'name_version', 'symlink_target']
 
120
 
 
121
    def compatible_for_commit(self, previous_ie):
 
122
        compatible = True
 
123
        # different inv parent
 
124
        if previous_ie.parent_id != self.parent_id:
 
125
            compatible = False
 
126
        # renamed
 
127
        elif previous_ie.name != self.name:
 
128
            compatible = False
 
129
        # changed link target
 
130
        elif (hasattr(self,'symlink_target')
 
131
              and self.symlink_target != previous_ie.symlink_target):
 
132
            compatible = False
 
133
        return compatible
108
134
 
109
135
    def __init__(self, file_id, name, kind, parent_id, text_id=None):
110
136
        """Create an InventoryEntry
126
152
            raise BzrCheckError('InventoryEntry name %r is invalid' % name)
127
153
        
128
154
        self.text_version = None
129
 
        self.entry_version = None
 
155
        self.name_version = None
130
156
        self.text_sha1 = None
131
157
        self.text_size = None
132
158
        self.file_id = file_id
156
182
        l.sort()
157
183
        return l
158
184
 
 
185
    def check(self, checker, rev_id, inv, tree):
 
186
        if self.parent_id != None:
 
187
            if not inv.has_id(self.parent_id):
 
188
                raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
 
189
                        % (self.parent_id, rev_id))
 
190
        if self.kind == 'file':
 
191
            text_version = self.text_version
 
192
            t = (self.file_id, text_version)
 
193
            if t in checker.checked_texts:
 
194
                prev_sha = checker.checked_texts[t] 
 
195
                if prev_sha != self.text_sha1:
 
196
                    raise BzrCheckError('mismatched sha1 on {%s} in {%s}' %
 
197
                                        (self.file_id, rev_id))
 
198
                else:
 
199
                    checker.repeated_text_cnt += 1
 
200
                    return
 
201
            mutter('check version {%s} of {%s}', rev_id, self.file_id)
 
202
            file_lines = tree.get_file_lines(self.file_id)
 
203
            checker.checked_text_cnt += 1 
 
204
            if self.text_size != sum(map(len, file_lines)):
 
205
                raise BzrCheckError('text {%s} wrong size' % self.text_id)
 
206
            if self.text_sha1 != sha_strings(file_lines):
 
207
                raise BzrCheckError('text {%s} wrong sha1' % self.text_id)
 
208
            checker.checked_texts[t] = self.text_sha1
 
209
        elif self.kind == 'directory':
 
210
            if self.text_sha1 != None or self.text_size != None or self.text_id != None:
 
211
                raise BzrCheckError('directory {%s} has text in revision {%s}'
 
212
                        % (self.file_id, rev_id))
 
213
        elif self.kind == 'root_directory':
 
214
            pass
 
215
        elif self.kind == 'symlink':
 
216
            if self.text_sha1 != None or self.text_size != None or self.text_id != None:
 
217
                raise BzrCheckError('symlink {%s} has text in revision {%s}'
 
218
                        % (self.file_id, rev_id))
 
219
            if self.symlink_target == None:
 
220
                raise BzrCheckError('symlink {%s} has no target in revision {%s}'
 
221
                        % (self.file_id, rev_id))
 
222
        else:
 
223
            raise BzrCheckError('unknown entry kind %r in revision {%s}' % 
 
224
                                (self.kind, rev_id))
 
225
 
159
226
 
160
227
    def copy(self):
161
228
        other = InventoryEntry(self.file_id, self.name, self.kind,
162
 
                               self.parent_id, text_id=self.text_id)
 
229
                               self.parent_id)
 
230
        other.text_id = self.text_id
163
231
        other.text_sha1 = self.text_sha1
164
232
        other.text_size = self.text_size
165
233
        other.symlink_target = self.symlink_target
 
234
        other.text_version = self.text_version
 
235
        other.name_version = self.name_version
166
236
        # note that children are *not* copied; they're pulled across when
167
237
        # others are added
168
238
        return other
189
259
               and (self.parent_id == other.parent_id) \
190
260
               and (self.kind == other.kind) \
191
261
               and (self.text_version == other.text_version) \
192
 
               and (self.entry_version == other.entry_version)
 
262
               and (self.name_version == other.name_version)
193
263
 
194
264
    def __ne__(self, other):
195
265
        return not (self == other)
198
268
        raise ValueError('not hashable')
199
269
 
200
270
 
201
 
 
202
271
class RootEntry(InventoryEntry):
203
272
    def __init__(self, file_id):
204
273
        self.file_id = file_id
272
341
        self._byid = {self.root.file_id: self.root}
273
342
 
274
343
 
 
344
    def copy(self):
 
345
        other = Inventory(self.root.file_id)
 
346
        # copy recursively so we know directories will be added before
 
347
        # their children.  There are more efficient ways than this...
 
348
        for path, entry in self.iter_entries():
 
349
            if entry == self.root:
 
350
                continue
 
351
            other.add(entry.copy())
 
352
        return other
 
353
 
 
354
 
275
355
    def __iter__(self):
276
356
        return iter(self._byid)
277
357
 
481
561
 
482
562
 
483
563
    def __ne__(self, other):
484
 
        return not (self == other)
 
564
        return not self.__eq__(other)
485
565
 
486
566
 
487
567
    def __hash__(self):