~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Martin Pool
  • Date: 2005-05-17 06:56:16 UTC
  • Revision ID: mbp@sourcefrog.net-20050517065616-6f23381d6184a8aa
- add space for un-merged patches

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')
154
161
    
155
162
    def to_element(self):
156
163
        """Convert to XML element"""
157
 
        from bzrlib.xml import Element
158
 
        
159
164
        e = Element('entry')
160
165
 
161
166
        e.set('name', self.name)
206
211
 
207
212
    from_element = classmethod(from_element)
208
213
 
209
 
    def __eq__(self, other):
 
214
    def __cmp__(self, other):
 
215
        if self is other:
 
216
            return 0
210
217
        if not isinstance(other, InventoryEntry):
211
218
            return NotImplemented
212
219
 
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')
 
220
        return cmp(self.file_id, other.file_id) \
 
221
               or cmp(self.name, other.name) \
 
222
               or cmp(self.text_sha1, other.text_sha1) \
 
223
               or cmp(self.text_size, other.text_size) \
 
224
               or cmp(self.text_id, other.text_id) \
 
225
               or cmp(self.parent_id, other.parent_id) \
 
226
               or cmp(self.kind, other.kind)
227
227
 
228
228
 
229
229
 
235
235
        self.parent_id = None
236
236
        self.name = ''
237
237
 
238
 
    def __eq__(self, other):
 
238
    def __cmp__(self, other):
 
239
        if self is other:
 
240
            return 0
239
241
        if not isinstance(other, RootEntry):
240
242
            return NotImplemented
241
 
        
242
 
        return (self.file_id == other.file_id) \
243
 
               and (self.children == other.children)
244
 
 
245
 
 
246
 
 
247
 
class Inventory(object):
 
243
        return cmp(self.file_id, other.file_id) \
 
244
               or cmp(self.children, other.children)
 
245
 
 
246
 
 
247
 
 
248
class Inventory(XMLMixin):
248
249
    """Inventory of versioned files in a tree.
249
250
 
250
251
    This describes which file_id is present at each point in the tree,
262
263
    inserted, other than through the Inventory API.
263
264
 
264
265
    >>> inv = Inventory()
 
266
    >>> inv.write_xml(sys.stdout)
 
267
    <inventory>
 
268
    </inventory>
265
269
    >>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
266
270
    >>> inv['123-123'].name
267
271
    'hello.c'
277
281
 
278
282
    >>> [x[0] for x in inv.iter_entries()]
279
283
    ['hello.c']
280
 
    >>> inv = Inventory('TREE_ROOT-12345678-12345678')
281
 
    >>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
 
284
    
 
285
    >>> inv.write_xml(sys.stdout)
 
286
    <inventory>
 
287
    <entry file_id="123-123" kind="file" name="hello.c" />
 
288
    </inventory>
 
289
 
282
290
    """
283
 
    def __init__(self, root_id=ROOT_ID):
 
291
    def __init__(self):
284
292
        """Create or read an inventory.
285
293
 
286
294
        If a working directory is specified, the inventory is read
290
298
        The inventory is created with a default root directory, with
291
299
        an id of None.
292
300
        """
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)
 
301
        self.root = RootEntry(ROOT_ID)
298
302
        self._byid = {self.root.file_id: self.root}
299
303
 
300
304
 
322
326
            if ie.kind == 'directory':
323
327
                for cn, cie in self.iter_entries(from_dir=ie.file_id):
324
328
                    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.
 
329
                    
 
330
 
 
331
 
 
332
    def directories(self):
 
333
        """Return (path, entry) pairs for all directories.
331
334
        """
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))
 
335
        def descend(parent_ie):
 
336
            parent_name = parent_ie.name
 
337
            yield parent_name, parent_ie
 
338
 
 
339
            # directory children in sorted order
 
340
            dn = []
 
341
            for ie in parent_ie.children.itervalues():
339
342
                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))
 
343
                    dn.append((ie.name, ie))
 
344
            dn.sort()
352
345
            
353
 
            kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
354
 
            kids.sort()
 
346
            for name, child_ie in dn:
 
347
                for sub_name, sub_ie in descend(child_ie):
 
348
                    yield appendpath(parent_name, sub_name), sub_ie
355
349
 
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
 
350
        for name, ie in descend(self.root):
 
351
            yield name, ie
361
352
        
362
353
 
363
354
 
404
395
        To add  a file to a branch ready to be committed, use Branch.add,
405
396
        which calls this."""
406
397
        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
 
398
            bailout("inventory already contains entry with id {%s}" % entry.file_id)
411
399
 
412
400
        try:
413
401
            parent = self._byid[entry.parent_id]
414
402
        except KeyError:
415
 
            raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
 
403
            bailout("parent_id {%s} not in inventory" % entry.parent_id)
416
404
 
417
405
        if parent.children.has_key(entry.name):
418
 
            raise BzrError("%s is already versioned" %
 
406
            bailout("%s is already versioned" %
419
407
                    appendpath(self.id2path(parent.file_id), entry.name))
420
408
 
421
409
        self._byid[entry.file_id] = entry
426
414
        """Add entry from a path.
427
415
 
428
416
        The immediate parent must already be versioned"""
429
 
        from bzrlib.errors import NotVersionedError
430
 
        
431
417
        parts = bzrlib.osutils.splitpath(relpath)
432
418
        if len(parts) == 0:
433
 
            raise BzrError("cannot re-add root of inventory")
 
419
            bailout("cannot re-add root of inventory")
434
420
 
435
421
        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
 
 
 
422
            file_id = bzrlib.branch.gen_file_id(relpath)
 
423
 
 
424
        parent_id = self.path2id(parts[:-1])
 
425
        assert parent_id != None
444
426
        ie = InventoryEntry(file_id, parts[-1],
445
427
                            kind=kind, parent_id=parent_id)
446
428
        return self.add(ie)
472
454
        del self[ie.parent_id].children[ie.name]
473
455
 
474
456
 
 
457
    def id_set(self):
 
458
        return Set(self._byid)
 
459
 
 
460
 
475
461
    def to_element(self):
476
462
        """Convert to XML Element"""
477
 
        from bzrlib.xml import Element
478
 
        
479
463
        e = Element('inventory')
480
464
        e.text = '\n'
481
 
        if self.root.file_id not in (None, ROOT_ID):
482
 
            e.set('file_id', self.root.file_id)
483
465
        for path, ie in self.iter_entries():
484
466
            e.append(ie.to_element())
485
467
        return e
487
469
 
488
470
    def from_element(cls, elt):
489
471
        """Construct from XML Element
490
 
        
 
472
 
491
473
        >>> inv = Inventory()
492
474
        >>> inv.add(InventoryEntry('foo.c-123981239', 'foo.c', 'file', ROOT_ID))
493
475
        >>> elt = inv.to_element()
495
477
        >>> inv2 == inv
496
478
        True
497
479
        """
498
 
        # XXXX: doctest doesn't run this properly under python2.3
499
480
        assert elt.tag == 'inventory'
500
 
        root_id = elt.get('file_id') or ROOT_ID
501
 
        o = cls(root_id)
 
481
        o = cls()
502
482
        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)
 
483
            o.add(InventoryEntry.from_element(e))
507
484
        return o
508
485
        
509
486
    from_element = classmethod(from_element)
510
487
 
511
488
 
512
 
    def __eq__(self, other):
 
489
    def __cmp__(self, other):
513
490
        """Compare two sets by comparing their contents.
514
491
 
515
492
        >>> i1 = Inventory()
523
500
        >>> i1 == i2
524
501
        True
525
502
        """
 
503
        if self is other:
 
504
            return 0
 
505
        
526
506
        if not isinstance(other, Inventory):
527
507
            return NotImplemented
528
508
 
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
 
 
 
509
        if self.id_set() ^ other.id_set():
 
510
            return 1
 
511
 
 
512
        for file_id in self._byid:
 
513
            c = cmp(self[file_id], other[file_id])
 
514
            if c: return c
 
515
 
 
516
        return 0
543
517
 
544
518
 
545
519
    def get_idpath(self, file_id):
555
529
            try:
556
530
                ie = self._byid[file_id]
557
531
            except KeyError:
558
 
                raise BzrError("file_id {%s} not found in inventory" % file_id)
 
532
                bailout("file_id {%s} not found in inventory" % file_id)
559
533
            p.insert(0, ie.file_id)
560
534
            file_id = ie.parent_id
561
535
        return p
565
539
        """Return as a list the path to file_id."""
566
540
 
567
541
        # get all names, skipping root
568
 
        p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
 
542
        p = [self[fid].name for fid in self.get_idpath(file_id)[1:]]
569
543
        return os.sep.join(p)
570
544
            
571
545
 
615
589
 
616
590
        This does not move the working file."""
617
591
        if not is_valid_name(new_name):
618
 
            raise BzrError("not an acceptable filename: %r" % new_name)
 
592
            bailout("not an acceptable filename: %r" % new_name)
619
593
 
620
594
        new_parent = self._byid[new_parent_id]
621
595
        if new_name in new_parent.children:
622
 
            raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
 
596
            bailout("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
623
597
 
624
598
        new_parent_idpath = self.get_idpath(new_parent_id)
625
599
        if file_id in new_parent_idpath:
626
 
            raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
 
600
            bailout("cannot move directory %r into a subdirectory of itself, %r"
627
601
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
628
602
 
629
603
        file_ie = self._byid[file_id]