~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Martin Pool
  • Date: 2005-07-16 00:07:40 UTC
  • mfrom: (909.1.5)
  • Revision ID: mbp@sourcefrog.net-20050716000740-f2dcb8894a23fd2d
- merge aaron's bugfix branch
  up to abentley@panoramicfeedback.com-20050715134354-78f2bca607acb415

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