~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Martin Pool
  • Date: 2005-07-07 10:22:02 UTC
  • Revision ID: mbp@sourcefrog.net-20050707102201-2d2a13a25098b101
- rearrange and clear up merged weave

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
18
 
# TODO: Maybe store inventory_id in the file?  Not really needed.
19
 
 
20
 
 
21
18
# This should really be an id randomly assigned when the tree is
22
19
# created, but it's not for now.
23
20
ROOT_ID = "TREE_ROOT"
24
21
 
25
22
 
26
23
import sys, os.path, types, re
27
 
from sets import Set
28
 
 
29
 
try:
30
 
    from cElementTree import Element, ElementTree, SubElement
31
 
except ImportError:
32
 
    from elementtree.ElementTree import Element, ElementTree, SubElement
33
 
 
34
 
from xml import XMLMixin
35
 
from errors import bailout, BzrError
36
24
 
37
25
import bzrlib
 
26
from bzrlib.errors import BzrError, BzrCheckError
 
27
 
38
28
from bzrlib.osutils import uuid, quotefn, splitpath, joinpath, appendpath
39
29
from bzrlib.trace import mutter
40
30
 
41
 
class InventoryEntry(XMLMixin):
 
31
class InventoryEntry(object):
42
32
    """Description of a versioned file.
43
33
 
44
34
    An InventoryEntry has the following fields, which are also
72
62
    >>> i.add(InventoryEntry('2323', 'bye.c', 'file', '123'))
73
63
    Traceback (most recent call last):
74
64
    ...
75
 
    BzrError: ('inventory already contains entry with id {2323}', [])
 
65
    BzrError: inventory already contains entry with id {2323}
76
66
    >>> i.add(InventoryEntry('2324', 'bye.c', 'file', '123'))
77
67
    >>> i.add(InventoryEntry('2325', 'wibble', 'directory', '123'))
78
68
    >>> i.path2id('src/wibble')
101
91
 
102
92
    # TODO: split InventoryEntry into subclasses for files,
103
93
    # directories, etc etc.
 
94
 
 
95
    text_sha1 = None
 
96
    text_size = None
104
97
    
105
98
    def __init__(self, file_id, name, kind, parent_id, text_id=None):
106
99
        """Create an InventoryEntry
115
108
        '123'
116
109
        >>> e = InventoryEntry('123', 'src/hello.c', 'file', ROOT_ID)
117
110
        Traceback (most recent call last):
118
 
        BzrError: ("InventoryEntry name is not a simple filename: 'src/hello.c'", [])
 
111
        BzrCheckError: InventoryEntry name 'src/hello.c' is invalid
119
112
        """
120
 
        
121
 
        if len(splitpath(name)) != 1:
122
 
            bailout('InventoryEntry name is not a simple filename: %r'
123
 
                    % name)
 
113
        if '/' in name or '\\' in name:
 
114
            raise BzrCheckError('InventoryEntry name %r is invalid' % name)
124
115
        
125
116
        self.file_id = file_id
126
117
        self.name = name
127
118
        self.kind = kind
128
119
        self.text_id = text_id
129
120
        self.parent_id = parent_id
130
 
        self.text_sha1 = None
131
 
        self.text_size = None
132
121
        if kind == 'directory':
133
122
            self.children = {}
134
123
        elif kind == 'file':
149
138
                               self.parent_id, text_id=self.text_id)
150
139
        other.text_sha1 = self.text_sha1
151
140
        other.text_size = self.text_size
 
141
        # note that children are *not* copied; they're pulled across when
 
142
        # others are added
152
143
        return other
153
144
 
154
145
 
163
154
    
164
155
    def to_element(self):
165
156
        """Convert to XML element"""
 
157
        from bzrlib.xml import Element
 
158
        
166
159
        e = Element('entry')
167
160
 
168
161
        e.set('name', self.name)
213
206
 
214
207
    from_element = classmethod(from_element)
215
208
 
216
 
    def __cmp__(self, other):
217
 
        if self is other:
218
 
            return 0
 
209
    def __eq__(self, other):
219
210
        if not isinstance(other, InventoryEntry):
220
211
            return NotImplemented
221
212
 
222
 
        return cmp(self.file_id, other.file_id) \
223
 
               or cmp(self.name, other.name) \
224
 
               or cmp(self.text_sha1, other.text_sha1) \
225
 
               or cmp(self.text_size, other.text_size) \
226
 
               or cmp(self.text_id, other.text_id) \
227
 
               or cmp(self.parent_id, other.parent_id) \
228
 
               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')
229
227
 
230
228
 
231
229
 
237
235
        self.parent_id = None
238
236
        self.name = ''
239
237
 
240
 
    def __cmp__(self, other):
241
 
        if self is other:
242
 
            return 0
 
238
    def __eq__(self, other):
243
239
        if not isinstance(other, RootEntry):
244
240
            return NotImplemented
245
 
        return cmp(self.file_id, other.file_id) \
246
 
               or cmp(self.children, other.children)
247
 
 
248
 
 
249
 
 
250
 
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):
251
248
    """Inventory of versioned files in a tree.
252
249
 
253
250
    This describes which file_id is present at each point in the tree,
265
262
    inserted, other than through the Inventory API.
266
263
 
267
264
    >>> inv = Inventory()
268
 
    >>> inv.write_xml(sys.stdout)
269
 
    <inventory>
270
 
    </inventory>
271
265
    >>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
272
266
    >>> inv['123-123'].name
273
267
    'hello.c'
283
277
 
284
278
    >>> [x[0] for x in inv.iter_entries()]
285
279
    ['hello.c']
286
 
    
287
 
    >>> inv.write_xml(sys.stdout)
288
 
    <inventory>
289
 
    <entry file_id="123-123" kind="file" name="hello.c" />
290
 
    </inventory>
291
 
 
292
280
    """
293
 
 
294
 
    ## TODO: Make sure only canonical filenames are stored.
295
 
 
296
 
    ## TODO: Do something sensible about the possible collisions on
297
 
    ## case-losing filesystems.  Perhaps we should just always forbid
298
 
    ## such collisions.
299
 
 
300
 
    ## TODO: No special cases for root, rather just give it a file id
301
 
    ## like everything else.
302
 
 
303
 
    ## TODO: Probably change XML serialization to use nesting rather
304
 
    ## than parent_id pointers.
305
 
 
306
 
    ## TODO: Perhaps hold the ElementTree in memory and work directly
307
 
    ## on that rather than converting into Python objects every time?
308
 
 
309
281
    def __init__(self):
310
282
        """Create or read an inventory.
311
283
 
344
316
            if ie.kind == 'directory':
345
317
                for cn, cie in self.iter_entries(from_dir=ie.file_id):
346
318
                    yield os.path.join(name, cn), cie
347
 
                    
 
319
 
 
320
 
 
321
    def entries(self):
 
322
        """Return list of (path, ie) for all entries except the root.
 
323
 
 
324
        This may be faster than iter_entries.
 
325
        """
 
326
        accum = []
 
327
        def descend(dir_ie, dir_path):
 
328
            kids = dir_ie.children.items()
 
329
            kids.sort()
 
330
            for name, ie in kids:
 
331
                child_path = os.path.join(dir_path, name)
 
332
                accum.append((child_path, ie))
 
333
                if ie.kind == 'directory':
 
334
                    descend(ie, child_path)
 
335
 
 
336
        descend(self.root, '')
 
337
        return accum
348
338
 
349
339
 
350
340
    def directories(self):
351
 
        """Return (path, entry) pairs for all directories.
 
341
        """Return (path, entry) pairs for all directories, including the root.
352
342
        """
353
 
        def descend(parent_ie):
354
 
            parent_name = parent_ie.name
355
 
            yield parent_name, parent_ie
356
 
 
357
 
            # directory children in sorted order
358
 
            dn = []
359
 
            for ie in parent_ie.children.itervalues():
360
 
                if ie.kind == 'directory':
361
 
                    dn.append((ie.name, ie))
362
 
            dn.sort()
 
343
        accum = []
 
344
        def descend(parent_ie, parent_path):
 
345
            accum.append((parent_path, parent_ie))
363
346
            
364
 
            for name, child_ie in dn:
365
 
                for sub_name, sub_ie in descend(child_ie):
366
 
                    yield appendpath(parent_name, sub_name), sub_ie
 
347
            kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
 
348
            kids.sort()
367
349
 
368
 
        for name, ie in descend(self.root):
369
 
            yield name, ie
 
350
            for name, child_ie in kids:
 
351
                child_path = os.path.join(parent_path, name)
 
352
                descend(child_ie, child_path)
 
353
        descend(self.root, '')
 
354
        return accum
370
355
        
371
356
 
372
357
 
391
376
        >>> inv['123123'].name
392
377
        'hello.c'
393
378
        """
394
 
        if file_id == None:
395
 
            raise BzrError("can't look up file_id None")
396
 
            
397
379
        try:
398
380
            return self._byid[file_id]
399
381
        except KeyError:
400
 
            raise BzrError("file_id {%s} not in inventory" % file_id)
401
 
 
 
382
            if file_id == None:
 
383
                raise BzrError("can't look up file_id None")
 
384
            else:
 
385
                raise BzrError("file_id {%s} not in inventory" % file_id)
 
386
 
 
387
 
 
388
    def get_file_kind(self, file_id):
 
389
        return self._byid[file_id].kind
402
390
 
403
391
    def get_child(self, parent_id, filename):
404
392
        return self[parent_id].children.get(filename)
410
398
        To add  a file to a branch ready to be committed, use Branch.add,
411
399
        which calls this."""
412
400
        if entry.file_id in self._byid:
413
 
            bailout("inventory already contains entry with id {%s}" % entry.file_id)
 
401
            raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
414
402
 
415
403
        try:
416
404
            parent = self._byid[entry.parent_id]
417
405
        except KeyError:
418
 
            bailout("parent_id {%s} not in inventory" % entry.parent_id)
 
406
            raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
419
407
 
420
408
        if parent.children.has_key(entry.name):
421
 
            bailout("%s is already versioned" %
 
409
            raise BzrError("%s is already versioned" %
422
410
                    appendpath(self.id2path(parent.file_id), entry.name))
423
411
 
424
412
        self._byid[entry.file_id] = entry
429
417
        """Add entry from a path.
430
418
 
431
419
        The immediate parent must already be versioned"""
 
420
        from bzrlib.errors import NotVersionedError
 
421
        
432
422
        parts = bzrlib.osutils.splitpath(relpath)
433
423
        if len(parts) == 0:
434
 
            bailout("cannot re-add root of inventory")
 
424
            raise BzrError("cannot re-add root of inventory")
435
425
 
436
426
        if file_id == None:
437
 
            file_id = bzrlib.branch.gen_file_id(relpath)
438
 
 
439
 
        parent_id = self.path2id(parts[:-1])
440
 
        assert parent_id != None
 
427
            from bzrlib.branch import gen_file_id
 
428
            file_id = gen_file_id(relpath)
 
429
 
 
430
        parent_path = parts[:-1]
 
431
        parent_id = self.path2id(parent_path)
 
432
        if parent_id == None:
 
433
            raise NotVersionedError(parent_path)
 
434
 
441
435
        ie = InventoryEntry(file_id, parts[-1],
442
436
                            kind=kind, parent_id=parent_id)
443
437
        return self.add(ie)
469
463
        del self[ie.parent_id].children[ie.name]
470
464
 
471
465
 
472
 
    def id_set(self):
473
 
        return Set(self._byid)
474
 
 
475
 
 
476
466
    def to_element(self):
477
467
        """Convert to XML Element"""
 
468
        from bzrlib.xml import Element
 
469
        
478
470
        e = Element('inventory')
479
471
        e.text = '\n'
480
472
        for path, ie in self.iter_entries():
484
476
 
485
477
    def from_element(cls, elt):
486
478
        """Construct from XML Element
487
 
 
 
479
        
488
480
        >>> inv = Inventory()
489
481
        >>> inv.add(InventoryEntry('foo.c-123981239', 'foo.c', 'file', ROOT_ID))
490
482
        >>> elt = inv.to_element()
492
484
        >>> inv2 == inv
493
485
        True
494
486
        """
 
487
        # XXXX: doctest doesn't run this properly under python2.3
495
488
        assert elt.tag == 'inventory'
496
489
        o = cls()
497
490
        for e in elt:
501
494
    from_element = classmethod(from_element)
502
495
 
503
496
 
504
 
    def __cmp__(self, other):
 
497
    def __eq__(self, other):
505
498
        """Compare two sets by comparing their contents.
506
499
 
507
500
        >>> i1 = Inventory()
515
508
        >>> i1 == i2
516
509
        True
517
510
        """
518
 
        if self is other:
519
 
            return 0
520
 
        
521
511
        if not isinstance(other, Inventory):
522
512
            return NotImplemented
523
513
 
524
 
        if self.id_set() ^ other.id_set():
525
 
            return 1
526
 
 
527
 
        for file_id in self._byid:
528
 
            c = cmp(self[file_id], other[file_id])
529
 
            if c: return c
530
 
 
531
 
        return 0
 
514
        if len(self._byid) != len(other._byid):
 
515
            # shortcut: obviously not the same
 
516
            return False
 
517
 
 
518
        return self._byid == other._byid
 
519
 
 
520
 
 
521
    def __ne__(self, other):
 
522
        return not (self == other)
 
523
 
 
524
 
 
525
    def __hash__(self):
 
526
        raise ValueError('not hashable')
 
527
 
532
528
 
533
529
 
534
530
    def get_idpath(self, file_id):
544
540
            try:
545
541
                ie = self._byid[file_id]
546
542
            except KeyError:
547
 
                bailout("file_id {%s} not found in inventory" % file_id)
 
543
                raise BzrError("file_id {%s} not found in inventory" % file_id)
548
544
            p.insert(0, ie.file_id)
549
545
            file_id = ie.parent_id
550
546
        return p
604
600
 
605
601
        This does not move the working file."""
606
602
        if not is_valid_name(new_name):
607
 
            bailout("not an acceptable filename: %r" % new_name)
 
603
            raise BzrError("not an acceptable filename: %r" % new_name)
608
604
 
609
605
        new_parent = self._byid[new_parent_id]
610
606
        if new_name in new_parent.children:
611
 
            bailout("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
 
607
            raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
612
608
 
613
609
        new_parent_idpath = self.get_idpath(new_parent_id)
614
610
        if file_id in new_parent_idpath:
615
 
            bailout("cannot move directory %r into a subdirectory of itself, %r"
 
611
            raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
616
612
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
617
613
 
618
614
        file_ie = self._byid[file_id]