~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: aaron.bentley at utoronto
  • Date: 2005-08-19 12:06:01 UTC
  • mto: (1092.1.41) (1185.3.4) (974.1.47)
  • mto: This revision was merged to the branch mainline in revision 1110.
  • Revision ID: aaron.bentley@utoronto.ca-20050819120601-58525b75283a9c1c
Initial greedy fetch work

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
 
30
from bzrlib.errors import NotVersionedError
 
31
        
37
32
 
38
 
class InventoryEntry(XMLMixin):
 
33
class InventoryEntry(object):
39
34
    """Description of a versioned file.
40
35
 
41
36
    An InventoryEntry has the following fields, which are also
69
64
    >>> i.add(InventoryEntry('2323', 'bye.c', 'file', '123'))
70
65
    Traceback (most recent call last):
71
66
    ...
72
 
    BzrError: ('inventory already contains entry with id {2323}', [])
 
67
    BzrError: inventory already contains entry with id {2323}
73
68
    >>> i.add(InventoryEntry('2324', 'bye.c', 'file', '123'))
74
69
    >>> i.add(InventoryEntry('2325', 'wibble', 'directory', '123'))
75
70
    >>> i.path2id('src/wibble')
99
94
    # TODO: split InventoryEntry into subclasses for files,
100
95
    # directories, etc etc.
101
96
 
102
 
    text_sha1 = None
103
 
    text_size = None
104
 
    
 
97
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
98
                 'text_id', 'parent_id', 'children', ]
 
99
 
105
100
    def __init__(self, file_id, name, kind, parent_id, text_id=None):
106
101
        """Create an InventoryEntry
107
102
        
120
115
        if '/' in name or '\\' in name:
121
116
            raise BzrCheckError('InventoryEntry name %r is invalid' % name)
122
117
        
 
118
        self.text_sha1 = None
 
119
        self.text_size = None
 
120
    
123
121
        self.file_id = file_id
124
122
        self.name = name
125
123
        self.kind = kind
145
143
                               self.parent_id, text_id=self.text_id)
146
144
        other.text_sha1 = self.text_sha1
147
145
        other.text_size = self.text_size
 
146
        # note that children are *not* copied; they're pulled across when
 
147
        # others are added
148
148
        return other
149
149
 
150
150
 
159
159
    
160
160
    def to_element(self):
161
161
        """Convert to XML element"""
 
162
        from bzrlib.xml import Element
 
163
        
162
164
        e = Element('entry')
163
165
 
164
166
        e.set('name', self.name)
209
211
 
210
212
    from_element = classmethod(from_element)
211
213
 
212
 
    def __cmp__(self, other):
213
 
        if self is other:
214
 
            return 0
 
214
    def __eq__(self, other):
215
215
        if not isinstance(other, InventoryEntry):
216
216
            return NotImplemented
217
217
 
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)
 
218
        return (self.file_id == other.file_id) \
 
219
               and (self.name == other.name) \
 
220
               and (self.text_sha1 == other.text_sha1) \
 
221
               and (self.text_size == other.text_size) \
 
222
               and (self.text_id == other.text_id) \
 
223
               and (self.parent_id == other.parent_id) \
 
224
               and (self.kind == other.kind)
 
225
 
 
226
 
 
227
    def __ne__(self, other):
 
228
        return not (self == other)
 
229
 
 
230
    def __hash__(self):
 
231
        raise ValueError('not hashable')
225
232
 
226
233
 
227
234
 
233
240
        self.parent_id = None
234
241
        self.name = ''
235
242
 
236
 
    def __cmp__(self, other):
237
 
        if self is other:
238
 
            return 0
 
243
    def __eq__(self, other):
239
244
        if not isinstance(other, RootEntry):
240
245
            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):
 
246
        
 
247
        return (self.file_id == other.file_id) \
 
248
               and (self.children == other.children)
 
249
 
 
250
 
 
251
 
 
252
class Inventory(object):
247
253
    """Inventory of versioned files in a tree.
248
254
 
249
255
    This describes which file_id is present at each point in the tree,
261
267
    inserted, other than through the Inventory API.
262
268
 
263
269
    >>> inv = Inventory()
264
 
    >>> inv.write_xml(sys.stdout)
265
 
    <inventory>
266
 
    </inventory>
267
270
    >>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
268
271
    >>> inv['123-123'].name
269
272
    'hello.c'
279
282
 
280
283
    >>> [x[0] for x in inv.iter_entries()]
281
284
    ['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
 
 
 
285
    >>> inv = Inventory('TREE_ROOT-12345678-12345678')
 
286
    >>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
288
287
    """
289
 
    def __init__(self):
 
288
    def __init__(self, root_id=ROOT_ID):
290
289
        """Create or read an inventory.
291
290
 
292
291
        If a working directory is specified, the inventory is read
296
295
        The inventory is created with a default root directory, with
297
296
        an id of None.
298
297
        """
299
 
        self.root = RootEntry(ROOT_ID)
 
298
        # We are letting Branch(init=True) create a unique inventory
 
299
        # root id. Rather than generating a random one here.
 
300
        #if root_id is None:
 
301
        #    root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
 
302
        self.root = RootEntry(root_id)
300
303
        self._byid = {self.root.file_id: self.root}
301
304
 
302
305
 
324
327
            if ie.kind == 'directory':
325
328
                for cn, cie in self.iter_entries(from_dir=ie.file_id):
326
329
                    yield os.path.join(name, cn), cie
327
 
                    
 
330
 
 
331
 
 
332
    def entries(self):
 
333
        """Return list of (path, ie) for all entries except the root.
 
334
 
 
335
        This may be faster than iter_entries.
 
336
        """
 
337
        accum = []
 
338
        def descend(dir_ie, dir_path):
 
339
            kids = dir_ie.children.items()
 
340
            kids.sort()
 
341
            for name, ie in kids:
 
342
                child_path = os.path.join(dir_path, name)
 
343
                accum.append((child_path, ie))
 
344
                if ie.kind == 'directory':
 
345
                    descend(ie, child_path)
 
346
 
 
347
        descend(self.root, '')
 
348
        return accum
328
349
 
329
350
 
330
351
    def directories(self):
331
 
        """Return (path, entry) pairs for all directories.
 
352
        """Return (path, entry) pairs for all directories, including the root.
332
353
        """
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()
 
354
        accum = []
 
355
        def descend(parent_ie, parent_path):
 
356
            accum.append((parent_path, parent_ie))
343
357
            
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
 
358
            kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
 
359
            kids.sort()
347
360
 
348
 
        for name, ie in descend(self.root):
349
 
            yield name, ie
 
361
            for name, child_ie in kids:
 
362
                child_path = os.path.join(parent_path, name)
 
363
                descend(child_ie, child_path)
 
364
        descend(self.root, '')
 
365
        return accum
350
366
        
351
367
 
352
368
 
393
409
        To add  a file to a branch ready to be committed, use Branch.add,
394
410
        which calls this."""
395
411
        if entry.file_id in self._byid:
396
 
            bailout("inventory already contains entry with id {%s}" % entry.file_id)
 
412
            raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
 
413
 
 
414
        if entry.parent_id == ROOT_ID or entry.parent_id is None:
 
415
            entry.parent_id = self.root.file_id
397
416
 
398
417
        try:
399
418
            parent = self._byid[entry.parent_id]
400
419
        except KeyError:
401
 
            bailout("parent_id {%s} not in inventory" % entry.parent_id)
 
420
            raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
402
421
 
403
422
        if parent.children.has_key(entry.name):
404
 
            bailout("%s is already versioned" %
 
423
            raise BzrError("%s is already versioned" %
405
424
                    appendpath(self.id2path(parent.file_id), entry.name))
406
425
 
407
426
        self._byid[entry.file_id] = entry
412
431
        """Add entry from a path.
413
432
 
414
433
        The immediate parent must already be versioned"""
 
434
        from bzrlib.branch import gen_file_id
 
435
        
415
436
        parts = bzrlib.osutils.splitpath(relpath)
416
437
        if len(parts) == 0:
417
 
            bailout("cannot re-add root of inventory")
 
438
            raise BzrError("cannot re-add root of inventory")
418
439
 
419
440
        if file_id == None:
420
 
            file_id = bzrlib.branch.gen_file_id(relpath)
421
 
 
422
 
        parent_id = self.path2id(parts[:-1])
423
 
        assert parent_id != None
 
441
            file_id = gen_file_id(relpath)
 
442
 
 
443
        parent_path = parts[:-1]
 
444
        parent_id = self.path2id(parent_path)
 
445
        if parent_id == None:
 
446
            raise NotVersionedError(parent_path)
 
447
 
424
448
        ie = InventoryEntry(file_id, parts[-1],
425
449
                            kind=kind, parent_id=parent_id)
426
450
        return self.add(ie)
452
476
        del self[ie.parent_id].children[ie.name]
453
477
 
454
478
 
455
 
    def id_set(self):
456
 
        return Set(self._byid)
457
 
 
458
 
 
459
479
    def to_element(self):
460
480
        """Convert to XML Element"""
 
481
        from bzrlib.xml import Element
 
482
        
461
483
        e = Element('inventory')
462
484
        e.text = '\n'
 
485
        if self.root.file_id not in (None, ROOT_ID):
 
486
            e.set('file_id', self.root.file_id)
463
487
        for path, ie in self.iter_entries():
464
488
            e.append(ie.to_element())
465
489
        return e
467
491
 
468
492
    def from_element(cls, elt):
469
493
        """Construct from XML Element
470
 
 
 
494
        
471
495
        >>> inv = Inventory()
472
496
        >>> inv.add(InventoryEntry('foo.c-123981239', 'foo.c', 'file', ROOT_ID))
473
497
        >>> elt = inv.to_element()
475
499
        >>> inv2 == inv
476
500
        True
477
501
        """
 
502
        # XXXX: doctest doesn't run this properly under python2.3
478
503
        assert elt.tag == 'inventory'
479
 
        o = cls()
 
504
        root_id = elt.get('file_id') or ROOT_ID
 
505
        o = cls(root_id)
480
506
        for e in elt:
481
 
            o.add(InventoryEntry.from_element(e))
 
507
            ie = InventoryEntry.from_element(e)
 
508
            if ie.parent_id == ROOT_ID:
 
509
                ie.parent_id = root_id
 
510
            o.add(ie)
482
511
        return o
483
512
        
484
513
    from_element = classmethod(from_element)
485
514
 
486
515
 
487
 
    def __cmp__(self, other):
 
516
    def __eq__(self, other):
488
517
        """Compare two sets by comparing their contents.
489
518
 
490
519
        >>> i1 = Inventory()
498
527
        >>> i1 == i2
499
528
        True
500
529
        """
501
 
        if self is other:
502
 
            return 0
503
 
        
504
530
        if not isinstance(other, Inventory):
505
531
            return NotImplemented
506
532
 
507
 
        if self.id_set() ^ other.id_set():
508
 
            return 1
509
 
 
510
 
        for file_id in self._byid:
511
 
            c = cmp(self[file_id], other[file_id])
512
 
            if c: return c
513
 
 
514
 
        return 0
 
533
        if len(self._byid) != len(other._byid):
 
534
            # shortcut: obviously not the same
 
535
            return False
 
536
 
 
537
        return self._byid == other._byid
 
538
 
 
539
 
 
540
    def __ne__(self, other):
 
541
        return not (self == other)
 
542
 
 
543
 
 
544
    def __hash__(self):
 
545
        raise ValueError('not hashable')
 
546
 
515
547
 
516
548
 
517
549
    def get_idpath(self, file_id):
527
559
            try:
528
560
                ie = self._byid[file_id]
529
561
            except KeyError:
530
 
                bailout("file_id {%s} not found in inventory" % file_id)
 
562
                raise BzrError("file_id {%s} not found in inventory" % file_id)
531
563
            p.insert(0, ie.file_id)
532
564
            file_id = ie.parent_id
533
565
        return p
537
569
        """Return as a list the path to file_id."""
538
570
 
539
571
        # get all names, skipping root
540
 
        p = [self[fid].name for fid in self.get_idpath(file_id)[1:]]
 
572
        p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
541
573
        return os.sep.join(p)
542
574
            
543
575
 
587
619
 
588
620
        This does not move the working file."""
589
621
        if not is_valid_name(new_name):
590
 
            bailout("not an acceptable filename: %r" % new_name)
 
622
            raise BzrError("not an acceptable filename: %r" % new_name)
591
623
 
592
624
        new_parent = self._byid[new_parent_id]
593
625
        if new_name in new_parent.children:
594
 
            bailout("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
 
626
            raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
595
627
 
596
628
        new_parent_idpath = self.get_idpath(new_parent_id)
597
629
        if file_id in new_parent_idpath:
598
 
            bailout("cannot move directory %r into a subdirectory of itself, %r"
 
630
            raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
599
631
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
600
632
 
601
633
        file_ie = self._byid[file_id]