~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Martin Pool
  • Date: 2005-09-13 05:22:41 UTC
  • Revision ID: mbp@sourcefrog.net-20050913052241-52dbd8e8ced620f6
- better BZR_DEBUG trace output

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"
27
35
 
28
36
from bzrlib.osutils import uuid, quotefn, splitpath, joinpath, appendpath
29
37
from bzrlib.trace import mutter
 
38
from bzrlib.errors import NotVersionedError
 
39
        
30
40
 
31
41
class InventoryEntry(object):
32
42
    """Description of a versioned file.
34
44
    An InventoryEntry has the following fields, which are also
35
45
    present in the XML inventory-entry element:
36
46
 
37
 
    * *file_id*
38
 
    * *name*: (only the basename within the directory, must not
39
 
      contain slashes)
40
 
    * *kind*: "directory" or "file"
41
 
    * *directory_id*: (if absent/null means the branch root directory)
42
 
    * *text_sha1*: only for files
43
 
    * *text_size*: in bytes, only for files 
44
 
    * *text_id*: identifier for the text version, only for files
45
 
 
46
 
    InventoryEntries can also exist inside a WorkingTree
47
 
    inventory, in which case they are not yet bound to a
48
 
    particular revision of the file.  In that case the text_sha1,
49
 
    text_size and text_id are absent.
50
 
 
 
47
    file_id
 
48
 
 
49
    name
 
50
        (within the parent directory)
 
51
 
 
52
    kind
 
53
        'directory' or 'file'
 
54
 
 
55
    parent_id
 
56
        file_id of the parent directory, or ROOT_ID
 
57
 
 
58
    entry_version
 
59
        the revision_id in which the name or parent of this file was
 
60
        last changed
 
61
 
 
62
    text_sha1
 
63
        sha-1 of the text of the file
 
64
        
 
65
    text_size
 
66
        size in bytes of the text of the file
 
67
        
 
68
    text_version
 
69
        the revision_id in which the text of this file was introduced
 
70
 
 
71
    (reading a version 4 tree created a text_id field.)
51
72
 
52
73
    >>> i = Inventory()
53
74
    >>> i.path2id('')
54
75
    'TREE_ROOT'
55
76
    >>> i.add(InventoryEntry('123', 'src', 'directory', ROOT_ID))
 
77
    InventoryEntry('123', 'src', kind='directory', parent_id='TREE_ROOT')
56
78
    >>> i.add(InventoryEntry('2323', 'hello.c', 'file', parent_id='123'))
 
79
    InventoryEntry('2323', 'hello.c', kind='file', parent_id='123')
57
80
    >>> for j in i.iter_entries():
58
81
    ...   print j
59
82
    ... 
64
87
    ...
65
88
    BzrError: inventory already contains entry with id {2323}
66
89
    >>> i.add(InventoryEntry('2324', 'bye.c', 'file', '123'))
 
90
    InventoryEntry('2324', 'bye.c', kind='file', parent_id='123')
67
91
    >>> i.add(InventoryEntry('2325', 'wibble', 'directory', '123'))
 
92
    InventoryEntry('2325', 'wibble', kind='directory', parent_id='123')
68
93
    >>> i.path2id('src/wibble')
69
94
    '2325'
70
95
    >>> '2325' in i
71
96
    True
72
97
    >>> i.add(InventoryEntry('2326', 'wibble.c', 'file', '2325'))
 
98
    InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
73
99
    >>> i['2326']
74
100
    InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
75
101
    >>> for j in i.iter_entries():
83
109
    src/wibble/wibble.c
84
110
    >>> i.id2path('2326')
85
111
    'src/wibble/wibble.c'
86
 
 
87
 
    TODO: Maybe also keep the full path of the entry, and the children?
88
 
           But those depend on its position within a particular inventory, and
89
 
           it would be nice not to need to hold the backpointer here.
90
112
    """
91
 
 
92
 
    # TODO: split InventoryEntry into subclasses for files,
93
 
    # directories, etc etc.
94
 
 
95
 
    text_sha1 = None
96
 
    text_size = None
97
113
    
 
114
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
115
                 'text_id', 'parent_id', 'children',
 
116
                 'text_version', 'entry_version', ]
 
117
 
 
118
 
98
119
    def __init__(self, file_id, name, kind, parent_id, text_id=None):
99
120
        """Create an InventoryEntry
100
121
        
110
131
        Traceback (most recent call last):
111
132
        BzrCheckError: InventoryEntry name 'src/hello.c' is invalid
112
133
        """
 
134
        assert isinstance(name, basestring), name
113
135
        if '/' in name or '\\' in name:
114
136
            raise BzrCheckError('InventoryEntry name %r is invalid' % name)
115
137
        
 
138
        self.text_version = None
 
139
        self.entry_version = None
 
140
        self.text_sha1 = None
 
141
        self.text_size = None
116
142
        self.file_id = file_id
117
143
        self.name = name
118
144
        self.kind = kind
135
161
 
136
162
    def copy(self):
137
163
        other = InventoryEntry(self.file_id, self.name, self.kind,
138
 
                               self.parent_id, text_id=self.text_id)
 
164
                               self.parent_id)
 
165
        other.text_id = self.text_id
139
166
        other.text_sha1 = self.text_sha1
140
167
        other.text_size = self.text_size
 
168
        other.text_version = self.text_version
141
169
        # note that children are *not* copied; they're pulled across when
142
170
        # others are added
143
171
        return other
152
180
                   self.parent_id))
153
181
 
154
182
    
155
 
    def to_element(self):
156
 
        """Convert to XML element"""
157
 
        from bzrlib.xml import Element
158
 
        
159
 
        e = Element('entry')
160
 
 
161
 
        e.set('name', self.name)
162
 
        e.set('file_id', self.file_id)
163
 
        e.set('kind', self.kind)
164
 
 
165
 
        if self.text_size != None:
166
 
            e.set('text_size', '%d' % self.text_size)
167
 
            
168
 
        for f in ['text_id', 'text_sha1']:
169
 
            v = getattr(self, f)
170
 
            if v != None:
171
 
                e.set(f, v)
172
 
 
173
 
        # to be conservative, we don't externalize the root pointers
174
 
        # for now, leaving them as null in the xml form.  in a future
175
 
        # version it will be implied by nested elements.
176
 
        if self.parent_id != ROOT_ID:
177
 
            assert isinstance(self.parent_id, basestring)
178
 
            e.set('parent_id', self.parent_id)
179
 
 
180
 
        e.tail = '\n'
181
 
            
182
 
        return e
183
 
 
184
 
 
185
 
    def from_element(cls, elt):
186
 
        assert elt.tag == 'entry'
187
 
 
188
 
        ## original format inventories don't have a parent_id for
189
 
        ## nodes in the root directory, but it's cleaner to use one
190
 
        ## internally.
191
 
        parent_id = elt.get('parent_id')
192
 
        if parent_id == None:
193
 
            parent_id = ROOT_ID
194
 
 
195
 
        self = cls(elt.get('file_id'), elt.get('name'), elt.get('kind'), parent_id)
196
 
        self.text_id = elt.get('text_id')
197
 
        self.text_sha1 = elt.get('text_sha1')
198
 
        
199
 
        ## mutter("read inventoryentry: %r" % (elt.attrib))
200
 
 
201
 
        v = elt.get('text_size')
202
 
        self.text_size = v and int(v)
203
 
 
204
 
        return self
205
 
            
206
 
 
207
 
    from_element = classmethod(from_element)
208
 
 
209
183
    def __eq__(self, other):
210
184
        if not isinstance(other, InventoryEntry):
211
185
            return NotImplemented
216
190
               and (self.text_size == other.text_size) \
217
191
               and (self.text_id == other.text_id) \
218
192
               and (self.parent_id == other.parent_id) \
219
 
               and (self.kind == other.kind)
 
193
               and (self.kind == other.kind) \
 
194
               and (self.text_version == other.text_version) \
 
195
               and (self.entry_version == other.entry_version)
220
196
 
221
197
 
222
198
    def __ne__(self, other):
263
239
 
264
240
    >>> inv = Inventory()
265
241
    >>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
 
242
    InventoryEntry('123-123', 'hello.c', kind='file', parent_id='TREE_ROOT')
266
243
    >>> inv['123-123'].name
267
244
    'hello.c'
268
245
 
277
254
 
278
255
    >>> [x[0] for x in inv.iter_entries()]
279
256
    ['hello.c']
 
257
    >>> inv = Inventory('TREE_ROOT-12345678-12345678')
 
258
    >>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
 
259
    InventoryEntry('123-123', 'hello.c', kind='file', parent_id='TREE_ROOT-12345678-12345678')
280
260
    """
281
 
    def __init__(self):
 
261
    def __init__(self, root_id=ROOT_ID):
282
262
        """Create or read an inventory.
283
263
 
284
264
        If a working directory is specified, the inventory is read
288
268
        The inventory is created with a default root directory, with
289
269
        an id of None.
290
270
        """
291
 
        self.root = RootEntry(ROOT_ID)
 
271
        # We are letting Branch(init=True) create a unique inventory
 
272
        # root id. Rather than generating a random one here.
 
273
        #if root_id is None:
 
274
        #    root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
 
275
        self.root = RootEntry(root_id)
292
276
        self._byid = {self.root.file_id: self.root}
293
277
 
294
278
 
 
279
    def copy(self):
 
280
        other = Inventory(self.root.file_id)
 
281
        # copy recursively so we know directories will be added before
 
282
        # their children.  There are more efficient ways than this...
 
283
        for path, entry in self.iter_entries():
 
284
            if entry == self.root:
 
285
                continue
 
286
            other.add(entry.copy())
 
287
        return other
 
288
 
 
289
 
295
290
    def __iter__(self):
296
291
        return iter(self._byid)
297
292
 
360
355
 
361
356
        >>> inv = Inventory()
362
357
        >>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
 
358
        InventoryEntry('123', 'foo.c', kind='file', parent_id='TREE_ROOT')
363
359
        >>> '123' in inv
364
360
        True
365
361
        >>> '456' in inv
373
369
 
374
370
        >>> inv = Inventory()
375
371
        >>> inv.add(InventoryEntry('123123', 'hello.c', 'file', ROOT_ID))
 
372
        InventoryEntry('123123', 'hello.c', kind='file', parent_id='TREE_ROOT')
376
373
        >>> inv['123123'].name
377
374
        'hello.c'
378
375
        """
396
393
        """Add entry to inventory.
397
394
 
398
395
        To add  a file to a branch ready to be committed, use Branch.add,
399
 
        which calls this."""
 
396
        which calls this.
 
397
 
 
398
        Returns the new entry object.
 
399
        """
400
400
        if entry.file_id in self._byid:
401
401
            raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
402
402
 
 
403
        if entry.parent_id == ROOT_ID or entry.parent_id is None:
 
404
            entry.parent_id = self.root.file_id
 
405
 
403
406
        try:
404
407
            parent = self._byid[entry.parent_id]
405
408
        except KeyError:
411
414
 
412
415
        self._byid[entry.file_id] = entry
413
416
        parent.children[entry.name] = entry
 
417
        return entry
414
418
 
415
419
 
416
420
    def add_path(self, relpath, kind, file_id=None):
417
421
        """Add entry from a path.
418
422
 
419
 
        The immediate parent must already be versioned"""
420
 
        from bzrlib.errors import NotVersionedError
 
423
        The immediate parent must already be versioned.
 
424
 
 
425
        Returns the new entry object."""
 
426
        from bzrlib.branch import gen_file_id
421
427
        
422
428
        parts = bzrlib.osutils.splitpath(relpath)
423
429
        if len(parts) == 0:
424
430
            raise BzrError("cannot re-add root of inventory")
425
431
 
426
432
        if file_id == None:
427
 
            from bzrlib.branch import gen_file_id
428
433
            file_id = gen_file_id(relpath)
429
434
 
430
435
        parent_path = parts[:-1]
442
447
 
443
448
        >>> inv = Inventory()
444
449
        >>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
 
450
        InventoryEntry('123', 'foo.c', kind='file', parent_id='TREE_ROOT')
445
451
        >>> '123' in inv
446
452
        True
447
453
        >>> del inv['123']
463
469
        del self[ie.parent_id].children[ie.name]
464
470
 
465
471
 
466
 
    def to_element(self):
467
 
        """Convert to XML Element"""
468
 
        from bzrlib.xml import Element
469
 
        
470
 
        e = Element('inventory')
471
 
        e.text = '\n'
472
 
        for path, ie in self.iter_entries():
473
 
            e.append(ie.to_element())
474
 
        return e
475
 
    
476
 
 
477
 
    def from_element(cls, elt):
478
 
        """Construct from XML Element
479
 
 
480
 
        >>> inv = Inventory()
481
 
        >>> inv.add(InventoryEntry('foo.c-123981239', 'foo.c', 'file', ROOT_ID))
482
 
        >>> elt = inv.to_element()
483
 
        >>> inv2 = Inventory.from_element(elt)
484
 
        >>> inv2 == inv
485
 
        True
486
 
        """
487
 
        assert elt.tag == 'inventory'
488
 
        o = cls()
489
 
        for e in elt:
490
 
            o.add(InventoryEntry.from_element(e))
491
 
        return o
492
 
        
493
 
    from_element = classmethod(from_element)
494
 
 
495
 
 
496
472
    def __eq__(self, other):
497
473
        """Compare two sets by comparing their contents.
498
474
 
501
477
        >>> i1 == i2
502
478
        True
503
479
        >>> i1.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
 
480
        InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
504
481
        >>> i1 == i2
505
482
        False
506
483
        >>> i2.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
 
484
        InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
507
485
        >>> i1 == i2
508
486
        True
509
487
        """
525
503
        raise ValueError('not hashable')
526
504
 
527
505
 
528
 
 
529
506
    def get_idpath(self, file_id):
530
507
        """Return a list of file_ids for the path to an entry.
531
508
 
549
526
        """Return as a list the path to file_id."""
550
527
 
551
528
        # get all names, skipping root
552
 
        p = [self[fid].name for fid in self.get_idpath(file_id)[1:]]
 
529
        p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
553
530
        return os.sep.join(p)
554
531
            
555
532
 
624
601
 
625
602
 
626
603
 
627
 
_NAME_RE = re.compile(r'^[^/\\]+$')
 
604
_NAME_RE = None
628
605
 
629
606
def is_valid_name(name):
 
607
    global _NAME_RE
 
608
    if _NAME_RE == None:
 
609
        _NAME_RE = re.compile(r'^[^/\\]+$')
 
610
        
630
611
    return bool(_NAME_RE.match(name))