~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Martin Pool
  • Date: 2005-05-10 08:15:58 UTC
  • Revision ID: mbp@sourcefrog.net-20050510081558-9a38e2c46ba4ebc4
- Patch from Fredrik Lundh to check Python version and 
  try to find a better one if it's too old.

  Patched to try to prevent infinite loops in wierd configurations,
  and to log to stderr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 
22
22
 
23
23
import sys, os.path, types, re
 
24
from sets import Set
 
25
 
 
26
try:
 
27
    from cElementTree import Element, ElementTree, SubElement
 
28
except ImportError:
 
29
    from elementtree.ElementTree import Element, ElementTree, SubElement
 
30
 
 
31
from xml import XMLMixin
 
32
from errors import bailout, BzrError, BzrCheckError
24
33
 
25
34
import bzrlib
26
 
from bzrlib.errors import BzrError, BzrCheckError
27
 
 
28
35
from bzrlib.osutils import uuid, quotefn, splitpath, joinpath, appendpath
29
36
from bzrlib.trace import mutter
30
37
 
31
 
class InventoryEntry(object):
 
38
class InventoryEntry(XMLMixin):
32
39
    """Description of a versioned file.
33
40
 
34
41
    An InventoryEntry has the following fields, which are also
62
69
    >>> i.add(InventoryEntry('2323', 'bye.c', 'file', '123'))
63
70
    Traceback (most recent call last):
64
71
    ...
65
 
    BzrError: inventory already contains entry with id {2323}
 
72
    BzrError: ('inventory already contains entry with id {2323}', [])
66
73
    >>> i.add(InventoryEntry('2324', 'bye.c', 'file', '123'))
67
74
    >>> i.add(InventoryEntry('2325', 'wibble', 'directory', '123'))
68
75
    >>> i.path2id('src/wibble')
138
145
                               self.parent_id, text_id=self.text_id)
139
146
        other.text_sha1 = self.text_sha1
140
147
        other.text_size = self.text_size
141
 
        # note that children are *not* copied; they're pulled across when
142
 
        # others are added
143
148
        return other
144
149
 
145
150
 
154
159
    
155
160
    def to_element(self):
156
161
        """Convert to XML element"""
157
 
        from bzrlib.xml import Element
158
 
        
159
162
        e = Element('entry')
160
163
 
161
164
        e.set('name', self.name)
206
209
 
207
210
    from_element = classmethod(from_element)
208
211
 
209
 
    def __eq__(self, other):
 
212
    def __cmp__(self, other):
 
213
        if self is other:
 
214
            return 0
210
215
        if not isinstance(other, InventoryEntry):
211
216
            return NotImplemented
212
217
 
213
 
        return (self.file_id == other.file_id) \
214
 
               and (self.name == other.name) \
215
 
               and (self.text_sha1 == other.text_sha1) \
216
 
               and (self.text_size == other.text_size) \
217
 
               and (self.text_id == other.text_id) \
218
 
               and (self.parent_id == other.parent_id) \
219
 
               and (self.kind == other.kind)
220
 
 
221
 
 
222
 
    def __ne__(self, other):
223
 
        return not (self == other)
224
 
 
225
 
    def __hash__(self):
226
 
        raise ValueError('not hashable')
 
218
        return cmp(self.file_id, other.file_id) \
 
219
               or cmp(self.name, other.name) \
 
220
               or cmp(self.text_sha1, other.text_sha1) \
 
221
               or cmp(self.text_size, other.text_size) \
 
222
               or cmp(self.text_id, other.text_id) \
 
223
               or cmp(self.parent_id, other.parent_id) \
 
224
               or cmp(self.kind, other.kind)
227
225
 
228
226
 
229
227
 
235
233
        self.parent_id = None
236
234
        self.name = ''
237
235
 
238
 
    def __eq__(self, other):
 
236
    def __cmp__(self, other):
 
237
        if self is other:
 
238
            return 0
239
239
        if not isinstance(other, RootEntry):
240
240
            return NotImplemented
241
 
        
242
 
        return (self.file_id == other.file_id) \
243
 
               and (self.children == other.children)
244
 
 
245
 
 
246
 
 
247
 
class Inventory(object):
 
241
        return cmp(self.file_id, other.file_id) \
 
242
               or cmp(self.children, other.children)
 
243
 
 
244
 
 
245
 
 
246
class Inventory(XMLMixin):
248
247
    """Inventory of versioned files in a tree.
249
248
 
250
249
    This describes which file_id is present at each point in the tree,
262
261
    inserted, other than through the Inventory API.
263
262
 
264
263
    >>> inv = Inventory()
 
264
    >>> inv.write_xml(sys.stdout)
 
265
    <inventory>
 
266
    </inventory>
265
267
    >>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
266
268
    >>> inv['123-123'].name
267
269
    'hello.c'
277
279
 
278
280
    >>> [x[0] for x in inv.iter_entries()]
279
281
    ['hello.c']
280
 
    >>> inv = Inventory('TREE_ROOT-12345678-12345678')
281
 
    >>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
 
282
    
 
283
    >>> inv.write_xml(sys.stdout)
 
284
    <inventory>
 
285
    <entry file_id="123-123" kind="file" name="hello.c" />
 
286
    </inventory>
 
287
 
282
288
    """
283
 
    def __init__(self, root_id=ROOT_ID):
 
289
    def __init__(self):
284
290
        """Create or read an inventory.
285
291
 
286
292
        If a working directory is specified, the inventory is read
290
296
        The inventory is created with a default root directory, with
291
297
        an id of None.
292
298
        """
293
 
        # We are letting Branch(init=True) create a unique inventory
294
 
        # root id. Rather than generating a random one here.
295
 
        #if root_id is None:
296
 
        #    root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
297
 
        self.root = RootEntry(root_id)
 
299
        self.root = RootEntry(ROOT_ID)
298
300
        self._byid = {self.root.file_id: self.root}
299
301
 
300
302
 
322
324
            if ie.kind == 'directory':
323
325
                for cn, cie in self.iter_entries(from_dir=ie.file_id):
324
326
                    yield os.path.join(name, cn), cie
325
 
 
326
 
 
327
 
    def entries(self):
328
 
        """Return list of (path, ie) for all entries except the root.
329
 
 
330
 
        This may be faster than iter_entries.
 
327
                    
 
328
 
 
329
 
 
330
    def directories(self):
 
331
        """Return (path, entry) pairs for all directories.
331
332
        """
332
 
        accum = []
333
 
        def descend(dir_ie, dir_path):
334
 
            kids = dir_ie.children.items()
335
 
            kids.sort()
336
 
            for name, ie in kids:
337
 
                child_path = os.path.join(dir_path, name)
338
 
                accum.append((child_path, ie))
 
333
        def descend(parent_ie):
 
334
            parent_name = parent_ie.name
 
335
            yield parent_name, parent_ie
 
336
 
 
337
            # directory children in sorted order
 
338
            dn = []
 
339
            for ie in parent_ie.children.itervalues():
339
340
                if ie.kind == 'directory':
340
 
                    descend(ie, child_path)
341
 
 
342
 
        descend(self.root, '')
343
 
        return accum
344
 
 
345
 
 
346
 
    def directories(self):
347
 
        """Return (path, entry) pairs for all directories, including the root.
348
 
        """
349
 
        accum = []
350
 
        def descend(parent_ie, parent_path):
351
 
            accum.append((parent_path, parent_ie))
 
341
                    dn.append((ie.name, ie))
 
342
            dn.sort()
352
343
            
353
 
            kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
354
 
            kids.sort()
 
344
            for name, child_ie in dn:
 
345
                for sub_name, sub_ie in descend(child_ie):
 
346
                    yield appendpath(parent_name, sub_name), sub_ie
355
347
 
356
 
            for name, child_ie in kids:
357
 
                child_path = os.path.join(parent_path, name)
358
 
                descend(child_ie, child_path)
359
 
        descend(self.root, '')
360
 
        return accum
 
348
        for name, ie in descend(self.root):
 
349
            yield name, ie
361
350
        
362
351
 
363
352
 
391
380
                raise BzrError("file_id {%s} not in inventory" % file_id)
392
381
 
393
382
 
394
 
    def get_file_kind(self, file_id):
395
 
        return self._byid[file_id].kind
396
 
 
397
383
    def get_child(self, parent_id, filename):
398
384
        return self[parent_id].children.get(filename)
399
385
 
404
390
        To add  a file to a branch ready to be committed, use Branch.add,
405
391
        which calls this."""
406
392
        if entry.file_id in self._byid:
407
 
            raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
408
 
 
409
 
        if entry.parent_id == ROOT_ID or entry.parent_id is None:
410
 
            entry.parent_id = self.root.file_id
 
393
            bailout("inventory already contains entry with id {%s}" % entry.file_id)
411
394
 
412
395
        try:
413
396
            parent = self._byid[entry.parent_id]
414
397
        except KeyError:
415
 
            raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
 
398
            bailout("parent_id {%s} not in inventory" % entry.parent_id)
416
399
 
417
400
        if parent.children.has_key(entry.name):
418
 
            raise BzrError("%s is already versioned" %
 
401
            bailout("%s is already versioned" %
419
402
                    appendpath(self.id2path(parent.file_id), entry.name))
420
403
 
421
404
        self._byid[entry.file_id] = entry
426
409
        """Add entry from a path.
427
410
 
428
411
        The immediate parent must already be versioned"""
429
 
        from bzrlib.errors import NotVersionedError
430
 
        
431
412
        parts = bzrlib.osutils.splitpath(relpath)
432
413
        if len(parts) == 0:
433
 
            raise BzrError("cannot re-add root of inventory")
 
414
            bailout("cannot re-add root of inventory")
434
415
 
435
416
        if file_id == None:
436
 
            from bzrlib.branch import gen_file_id
437
 
            file_id = gen_file_id(relpath)
438
 
 
439
 
        parent_path = parts[:-1]
440
 
        parent_id = self.path2id(parent_path)
441
 
        if parent_id == None:
442
 
            raise NotVersionedError(parent_path)
443
 
 
 
417
            file_id = bzrlib.branch.gen_file_id(relpath)
 
418
 
 
419
        parent_id = self.path2id(parts[:-1])
 
420
        assert parent_id != None
444
421
        ie = InventoryEntry(file_id, parts[-1],
445
422
                            kind=kind, parent_id=parent_id)
446
423
        return self.add(ie)
472
449
        del self[ie.parent_id].children[ie.name]
473
450
 
474
451
 
 
452
    def id_set(self):
 
453
        return Set(self._byid)
 
454
 
 
455
 
475
456
    def to_element(self):
476
457
        """Convert to XML Element"""
477
 
        from bzrlib.xml import Element
478
 
        
479
458
        e = Element('inventory')
480
459
        e.text = '\n'
481
 
        if self.root.file_id not in (None, ROOT_ID):
482
 
            e.set('file_id', self.root.file_id)
483
460
        for path, ie in self.iter_entries():
484
461
            e.append(ie.to_element())
485
462
        return e
487
464
 
488
465
    def from_element(cls, elt):
489
466
        """Construct from XML Element
490
 
        
 
467
 
491
468
        >>> inv = Inventory()
492
469
        >>> inv.add(InventoryEntry('foo.c-123981239', 'foo.c', 'file', ROOT_ID))
493
470
        >>> elt = inv.to_element()
495
472
        >>> inv2 == inv
496
473
        True
497
474
        """
498
 
        # XXXX: doctest doesn't run this properly under python2.3
499
475
        assert elt.tag == 'inventory'
500
 
        root_id = elt.get('file_id') or ROOT_ID
501
 
        o = cls(root_id)
 
476
        o = cls()
502
477
        for e in elt:
503
 
            ie = InventoryEntry.from_element(e)
504
 
            if ie.parent_id == ROOT_ID:
505
 
                ie.parent_id = root_id
506
 
            o.add(ie)
 
478
            o.add(InventoryEntry.from_element(e))
507
479
        return o
508
480
        
509
481
    from_element = classmethod(from_element)
510
482
 
511
483
 
512
 
    def __eq__(self, other):
 
484
    def __cmp__(self, other):
513
485
        """Compare two sets by comparing their contents.
514
486
 
515
487
        >>> i1 = Inventory()
523
495
        >>> i1 == i2
524
496
        True
525
497
        """
 
498
        if self is other:
 
499
            return 0
 
500
        
526
501
        if not isinstance(other, Inventory):
527
502
            return NotImplemented
528
503
 
529
 
        if len(self._byid) != len(other._byid):
530
 
            # shortcut: obviously not the same
531
 
            return False
532
 
 
533
 
        return self._byid == other._byid
534
 
 
535
 
 
536
 
    def __ne__(self, other):
537
 
        return not (self == other)
538
 
 
539
 
 
540
 
    def __hash__(self):
541
 
        raise ValueError('not hashable')
542
 
 
 
504
        if self.id_set() ^ other.id_set():
 
505
            return 1
 
506
 
 
507
        for file_id in self._byid:
 
508
            c = cmp(self[file_id], other[file_id])
 
509
            if c: return c
 
510
 
 
511
        return 0
543
512
 
544
513
 
545
514
    def get_idpath(self, file_id):
555
524
            try:
556
525
                ie = self._byid[file_id]
557
526
            except KeyError:
558
 
                raise BzrError("file_id {%s} not found in inventory" % file_id)
 
527
                bailout("file_id {%s} not found in inventory" % file_id)
559
528
            p.insert(0, ie.file_id)
560
529
            file_id = ie.parent_id
561
530
        return p
565
534
        """Return as a list the path to file_id."""
566
535
 
567
536
        # get all names, skipping root
568
 
        p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
 
537
        p = [self[fid].name for fid in self.get_idpath(file_id)[1:]]
569
538
        return os.sep.join(p)
570
539
            
571
540
 
615
584
 
616
585
        This does not move the working file."""
617
586
        if not is_valid_name(new_name):
618
 
            raise BzrError("not an acceptable filename: %r" % new_name)
 
587
            bailout("not an acceptable filename: %r" % new_name)
619
588
 
620
589
        new_parent = self._byid[new_parent_id]
621
590
        if new_name in new_parent.children:
622
 
            raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
 
591
            bailout("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
623
592
 
624
593
        new_parent_idpath = self.get_idpath(new_parent_id)
625
594
        if file_id in new_parent_idpath:
626
 
            raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
 
595
            bailout("cannot move directory %r into a subdirectory of itself, %r"
627
596
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
628
597
 
629
598
        file_ie = self._byid[file_id]