~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: mbp at sourcefrog
  • Date: 2005-04-06 03:38:35 UTC
  • Revision ID: mbp@sourcefrog.net-20050406033835-0a8142f2ed51db26f3a3681c
- Use a non-null file_id for the branch root directory.  At the moment
  this is fixed; in the future it should be stored in the directory
  and perhaps be randomized at each branch init.  It is not written
  out to the inventory at all as yet.

- Various branch code cleanups to support this.

- If an exception occurs, log traceback into .bzr.log and print a
  message saying it's there.

- New file-id-path command and more help.

- Some pychecker fixups.

- InventoryEntry constructor parameters now require an entry kind and
  a parent_id.

- Fix up cat command when reading a file from a previous revision.

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
 
19
19
# TODO: Maybe store inventory_id in the file?  Not really needed.
20
20
 
21
 
__copyright__ = "Copyright (C) 2005 Canonical Ltd."
22
21
__author__ = "Martin Pool <mbp@canonical.com>"
23
22
 
 
23
 
 
24
# This should really be an id randomly assigned when the tree is
 
25
# created, but it's not for now.
 
26
ROOT_ID = "TREE_ROOT"
 
27
 
 
28
 
24
29
import sys, os.path, types, re
25
30
from sets import Set
26
31
 
59
64
 
60
65
    >>> i = Inventory()
61
66
    >>> i.path2id('')
62
 
    >>> i.add(InventoryEntry('123', 'src', kind='directory'))
63
 
    >>> i.add(InventoryEntry('2323', 'hello.c', parent_id='123'))
 
67
    'TREE_ROOT'
 
68
    >>> i.add(InventoryEntry('123', 'src', 'directory', ROOT_ID))
 
69
    >>> i.add(InventoryEntry('2323', 'hello.c', 'file', parent_id='123'))
64
70
    >>> for j in i.iter_entries():
65
71
    ...   print j
66
72
    ... 
67
 
    ('src', InventoryEntry('123', 'src', kind='directory', parent_id=None))
 
73
    ('src', InventoryEntry('123', 'src', kind='directory', parent_id='TREE_ROOT'))
68
74
    ('src/hello.c', InventoryEntry('2323', 'hello.c', kind='file', parent_id='123'))
69
 
    >>> i.add(InventoryEntry('2323', 'bye.c', parent_id='123'))
 
75
    >>> i.add(InventoryEntry('2323', 'bye.c', 'file', '123'))
70
76
    Traceback (most recent call last):
71
77
    ...
72
78
    BzrError: ('inventory already contains entry with id {2323}', [])
73
 
    >>> i.add(InventoryEntry('2324', 'bye.c', parent_id='123'))
74
 
    >>> i.add(InventoryEntry('2325', 'wibble', parent_id='123', kind='directory'))
 
79
    >>> i.add(InventoryEntry('2324', 'bye.c', 'file', '123'))
 
80
    >>> i.add(InventoryEntry('2325', 'wibble', 'directory', '123'))
75
81
    >>> i.path2id('src/wibble')
76
82
    '2325'
77
83
    >>> '2325' in i
78
84
    True
79
 
    >>> i.add(InventoryEntry('2326', 'wibble.c', parent_id='2325'))
 
85
    >>> i.add(InventoryEntry('2326', 'wibble.c', 'file', '2325'))
80
86
    >>> i['2326']
81
87
    InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
82
88
    >>> for j in i.iter_entries():
95
101
           But those depend on its position within a particular inventory, and
96
102
           it would be nice not to need to hold the backpointer here.
97
103
    """
98
 
    def __init__(self, file_id, name, kind='file', text_id=None,
99
 
                 parent_id=None):
 
104
 
 
105
    # TODO: split InventoryEntry into subclasses for files,
 
106
    # directories, etc etc.
 
107
    
 
108
    def __init__(self, file_id, name, kind, parent_id, text_id=None):
100
109
        """Create an InventoryEntry
101
110
        
102
111
        The filename must be a single component, relative to the
103
112
        parent directory; it cannot be a whole path or relative name.
104
113
 
105
 
        >>> e = InventoryEntry('123', 'hello.c')
 
114
        >>> e = InventoryEntry('123', 'hello.c', 'file', ROOT_ID)
106
115
        >>> e.name
107
116
        'hello.c'
108
117
        >>> e.file_id
109
118
        '123'
110
 
        >>> e = InventoryEntry('123', 'src/hello.c')
 
119
        >>> e = InventoryEntry('123', 'src/hello.c', 'file', ROOT_ID)
111
120
        Traceback (most recent call last):
112
121
        BzrError: ("InventoryEntry name is not a simple filename: 'src/hello.c'", [])
113
122
        """
126
135
        self.text_size = None
127
136
        if kind == 'directory':
128
137
            self.children = {}
 
138
        else:
 
139
            assert kind == 'file'
129
140
 
130
141
 
131
142
    def sorted_children(self):
136
147
 
137
148
    def copy(self):
138
149
        other = InventoryEntry(self.file_id, self.name, self.kind,
139
 
                               self.text_id, self.parent_id)
 
150
                               self.parent_id, text_id=self.text_id)
140
151
        other.text_sha1 = self.text_sha1
141
152
        other.text_size = self.text_size
142
153
        return other
159
170
        e.set('file_id', self.file_id)
160
171
        e.set('kind', self.kind)
161
172
 
162
 
        if self.text_size is not None:
 
173
        if self.text_size != None:
163
174
            e.set('text_size', '%d' % self.text_size)
164
175
            
165
 
        for f in ['text_id', 'text_sha1', 'parent_id']:
 
176
        for f in ['text_id', 'text_sha1']:
166
177
            v = getattr(self, f)
167
 
            if v is not None:
 
178
            if v != None:
168
179
                e.set(f, v)
169
180
 
 
181
        # to be conservative, we don't externalize the root pointers
 
182
        # for now, leaving them as null in the xml form.  in a future
 
183
        # version it will be implied by nested elements.
 
184
        if self.parent_id != ROOT_ID:
 
185
            assert isinstance(self.parent_id, basestring)
 
186
            e.set('parent_id', self.parent_id)
 
187
 
170
188
        e.tail = '\n'
171
189
            
172
190
        return e
174
192
 
175
193
    def from_element(cls, elt):
176
194
        assert elt.tag == 'entry'
177
 
        self = cls(elt.get('file_id'), elt.get('name'), elt.get('kind'))
 
195
 
 
196
        ## original format inventories don't have a parent_id for
 
197
        ## nodes in the root directory, but it's cleaner to use one
 
198
        ## internally.
 
199
        parent_id = elt.get('parent_id')
 
200
        if parent_id == None:
 
201
            parent_id = ROOT_ID
 
202
 
 
203
        self = cls(elt.get('file_id'), elt.get('name'), elt.get('kind'), parent_id)
178
204
        self.text_id = elt.get('text_id')
179
205
        self.text_sha1 = elt.get('text_sha1')
180
 
        self.parent_id = elt.get('parent_id')
181
206
        
182
207
        ## mutter("read inventoryentry: %r" % (elt.attrib))
183
208
 
247
272
    >>> inv.write_xml(sys.stdout)
248
273
    <inventory>
249
274
    </inventory>
250
 
    >>> inv.add(InventoryEntry('123-123', 'hello.c'))
 
275
    >>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
251
276
    >>> inv['123-123'].name
252
277
    'hello.c'
253
278
 
291
316
        The inventory is created with a default root directory, with
292
317
        an id of None.
293
318
        """
294
 
        self.root = RootEntry(None)
295
 
        self._byid = {None: self.root}
 
319
        self.root = RootEntry(ROOT_ID)
 
320
        self._byid = {self.root.file_id: self.root}
296
321
 
297
322
 
298
323
    def __iter__(self):
322
347
                    
323
348
 
324
349
 
325
 
    def directories(self, from_dir=None):
 
350
    def directories(self):
326
351
        """Return (path, entry) pairs for all directories.
327
352
        """
328
353
        def descend(parent_ie):
349
374
        """True if this entry contains a file with given id.
350
375
 
351
376
        >>> inv = Inventory()
352
 
        >>> inv.add(InventoryEntry('123', 'foo.c'))
 
377
        >>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
353
378
        >>> '123' in inv
354
379
        True
355
380
        >>> '456' in inv
362
387
        """Return the entry for given file_id.
363
388
 
364
389
        >>> inv = Inventory()
365
 
        >>> inv.add(InventoryEntry('123123', 'hello.c'))
 
390
        >>> inv.add(InventoryEntry('123123', 'hello.c', 'file', ROOT_ID))
366
391
        >>> inv['123123'].name
367
392
        'hello.c'
368
393
        """
369
 
        return self._byid[file_id]
 
394
        if file_id == None:
 
395
            bailout("can't look up file_id None")
 
396
            
 
397
        try:
 
398
            return self._byid[file_id]
 
399
        except KeyError:
 
400
            bailout("file_id {%s} not in inventory" % file_id)
370
401
 
371
402
 
372
403
    def get_child(self, parent_id, filename):
384
415
        try:
385
416
            parent = self._byid[entry.parent_id]
386
417
        except KeyError:
387
 
            bailout("parent_id %r not in inventory" % entry.parent_id)
 
418
            bailout("parent_id {%s} not in inventory" % entry.parent_id)
388
419
 
389
420
        if parent.children.has_key(entry.name):
390
421
            bailout("%s is already versioned" %
402
433
        if len(parts) == 0:
403
434
            bailout("cannot re-add root of inventory")
404
435
 
405
 
        if file_id is None:
 
436
        if file_id == None:
406
437
            file_id = bzrlib.branch.gen_file_id(relpath)
407
438
 
408
439
        parent_id = self.path2id(parts[:-1])
 
440
        assert parent_id != None
409
441
        ie = InventoryEntry(file_id, parts[-1],
410
442
                            kind=kind, parent_id=parent_id)
411
443
        return self.add(ie)
415
447
        """Remove entry by id.
416
448
 
417
449
        >>> inv = Inventory()
418
 
        >>> inv.add(InventoryEntry('123', 'foo.c'))
 
450
        >>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
419
451
        >>> '123' in inv
420
452
        True
421
453
        >>> del inv['123']
454
486
        """Construct from XML Element
455
487
 
456
488
        >>> inv = Inventory()
457
 
        >>> inv.add(InventoryEntry('foo.c-123981239', 'foo.c'))
 
489
        >>> inv.add(InventoryEntry('foo.c-123981239', 'foo.c', 'file', ROOT_ID))
458
490
        >>> elt = inv.to_element()
459
491
        >>> inv2 = Inventory.from_element(elt)
460
492
        >>> inv2 == inv
476
508
        >>> i2 = Inventory()
477
509
        >>> i1 == i2
478
510
        True
479
 
        >>> i1.add(InventoryEntry('123', 'foo'))
 
511
        >>> i1.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
480
512
        >>> i1 == i2
481
513
        False
482
 
        >>> i2.add(InventoryEntry('123', 'foo'))
 
514
        >>> i2.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
483
515
        >>> i1 == i2
484
516
        True
485
517
        """
505
537
        The list contains one element for each directory followed by
506
538
        the id of the file itself.  So the length of the returned list
507
539
        is equal to the depth of the file in the tree, counting the
508
 
        root directory as depth 0.
 
540
        root directory as depth 1.
509
541
        """
510
542
        p = []
511
543
        while file_id != None:
512
 
            ie = self._byid[file_id]
 
544
            try:
 
545
                ie = self._byid[file_id]
 
546
            except KeyError:
 
547
                bailout("file_id {%s} not found in inventory" % file_id)
513
548
            p.insert(0, ie.file_id)
514
549
            file_id = ie.parent_id
515
550
        return p
517
552
 
518
553
    def id2path(self, file_id):
519
554
        """Return as a list the path to file_id."""
520
 
        p = []
521
 
        while file_id != None:
522
 
            ie = self._byid[file_id]
523
 
            p.insert(0, ie.name)
524
 
            file_id = ie.parent_id
 
555
 
 
556
        # get all names, skipping root
 
557
        p = [self[fid].name for fid in self.get_idpath(file_id)[1:]]
525
558
        return '/'.join(p)
526
559
            
527
560
 
534
567
 
535
568
        This returns the entry of the last component in the path,
536
569
        which may be either a file or a directory.
 
570
 
 
571
        Returns None iff the path is not found.
537
572
        """
538
573
        if isinstance(name, types.StringTypes):
539
574
            name = splitpath(name)
540
575
 
541
 
        parent = self[None]
 
576
        mutter("lookup path %r" % name)
 
577
 
 
578
        parent = self.root
542
579
        for f in name:
543
580
            try:
544
581
                cie = parent.children[f]
545
582
                assert cie.name == f
 
583
                assert cie.parent_id == parent.file_id
546
584
                parent = cie
547
585
            except KeyError:
548
586
                # or raise an error?
595
633
 
596
634
def is_valid_name(name):
597
635
    return bool(_NAME_RE.match(name))
598
 
 
599
 
 
600
 
 
601
 
if __name__ == '__main__':
602
 
    import doctest, inventory
603
 
    doctest.testmod(inventory)