~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

add a clean target

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
 
23
23
import sys, os.path, types, re
24
24
 
25
 
try:
26
 
    from cElementTree import Element, ElementTree, SubElement
27
 
except ImportError:
28
 
    from elementtree.ElementTree import Element, ElementTree, SubElement
29
 
 
30
 
from xml import XMLMixin
31
 
from errors import bailout, BzrError, BzrCheckError
32
 
 
33
25
import bzrlib
 
26
from bzrlib.errors import BzrError, BzrCheckError
 
27
 
34
28
from bzrlib.osutils import uuid, quotefn, splitpath, joinpath, appendpath
35
29
from bzrlib.trace import mutter
 
30
from bzrlib.errors import NotVersionedError
 
31
        
36
32
 
37
 
class InventoryEntry(XMLMixin):
 
33
class InventoryEntry(object):
38
34
    """Description of a versioned file.
39
35
 
40
36
    An InventoryEntry has the following fields, which are also
59
55
    >>> i.path2id('')
60
56
    'TREE_ROOT'
61
57
    >>> i.add(InventoryEntry('123', 'src', 'directory', ROOT_ID))
 
58
    InventoryEntry('123', 'src', kind='directory', parent_id='TREE_ROOT')
62
59
    >>> i.add(InventoryEntry('2323', 'hello.c', 'file', parent_id='123'))
 
60
    InventoryEntry('2323', 'hello.c', kind='file', parent_id='123')
63
61
    >>> for j in i.iter_entries():
64
62
    ...   print j
65
63
    ... 
68
66
    >>> i.add(InventoryEntry('2323', 'bye.c', 'file', '123'))
69
67
    Traceback (most recent call last):
70
68
    ...
71
 
    BzrError: ('inventory already contains entry with id {2323}', [])
 
69
    BzrError: inventory already contains entry with id {2323}
72
70
    >>> i.add(InventoryEntry('2324', 'bye.c', 'file', '123'))
 
71
    InventoryEntry('2324', 'bye.c', kind='file', parent_id='123')
73
72
    >>> i.add(InventoryEntry('2325', 'wibble', 'directory', '123'))
 
73
    InventoryEntry('2325', 'wibble', kind='directory', parent_id='123')
74
74
    >>> i.path2id('src/wibble')
75
75
    '2325'
76
76
    >>> '2325' in i
77
77
    True
78
78
    >>> i.add(InventoryEntry('2326', 'wibble.c', 'file', '2325'))
 
79
    InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
79
80
    >>> i['2326']
80
81
    InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
81
82
    >>> for j in i.iter_entries():
98
99
    # TODO: split InventoryEntry into subclasses for files,
99
100
    # directories, etc etc.
100
101
 
101
 
    text_sha1 = None
102
 
    text_size = None
103
 
    
 
102
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
103
                 'text_id', 'parent_id', 'children',
 
104
                 'text_version', 'entry_version', ]
 
105
 
 
106
 
104
107
    def __init__(self, file_id, name, kind, parent_id, text_id=None):
105
108
        """Create an InventoryEntry
106
109
        
116
119
        Traceback (most recent call last):
117
120
        BzrCheckError: InventoryEntry name 'src/hello.c' is invalid
118
121
        """
 
122
        assert isinstance(name, basestring), name
119
123
        if '/' in name or '\\' in name:
120
124
            raise BzrCheckError('InventoryEntry name %r is invalid' % name)
121
125
        
 
126
        self.text_version = None
 
127
        self.entry_version = None
 
128
        self.text_sha1 = None
 
129
        self.text_size = None
122
130
        self.file_id = file_id
123
131
        self.name = name
124
132
        self.kind = kind
158
166
                   self.parent_id))
159
167
 
160
168
    
161
 
    def to_element(self):
162
 
        """Convert to XML element"""
163
 
        e = Element('entry')
164
 
 
165
 
        e.set('name', self.name)
166
 
        e.set('file_id', self.file_id)
167
 
        e.set('kind', self.kind)
168
 
 
169
 
        if self.text_size != None:
170
 
            e.set('text_size', '%d' % self.text_size)
171
 
            
172
 
        for f in ['text_id', 'text_sha1']:
173
 
            v = getattr(self, f)
174
 
            if v != None:
175
 
                e.set(f, v)
176
 
 
177
 
        # to be conservative, we don't externalize the root pointers
178
 
        # for now, leaving them as null in the xml form.  in a future
179
 
        # version it will be implied by nested elements.
180
 
        if self.parent_id != ROOT_ID:
181
 
            assert isinstance(self.parent_id, basestring)
182
 
            e.set('parent_id', self.parent_id)
183
 
 
184
 
        e.tail = '\n'
185
 
            
186
 
        return e
187
 
 
188
 
 
189
 
    def from_element(cls, elt):
190
 
        assert elt.tag == 'entry'
191
 
 
192
 
        ## original format inventories don't have a parent_id for
193
 
        ## nodes in the root directory, but it's cleaner to use one
194
 
        ## internally.
195
 
        parent_id = elt.get('parent_id')
196
 
        if parent_id == None:
197
 
            parent_id = ROOT_ID
198
 
 
199
 
        self = cls(elt.get('file_id'), elt.get('name'), elt.get('kind'), parent_id)
200
 
        self.text_id = elt.get('text_id')
201
 
        self.text_sha1 = elt.get('text_sha1')
202
 
        
203
 
        ## mutter("read inventoryentry: %r" % (elt.attrib))
204
 
 
205
 
        v = elt.get('text_size')
206
 
        self.text_size = v and int(v)
207
 
 
208
 
        return self
209
 
            
210
 
 
211
 
    from_element = classmethod(from_element)
212
 
 
213
169
    def __eq__(self, other):
214
170
        if not isinstance(other, InventoryEntry):
215
171
            return NotImplemented
220
176
               and (self.text_size == other.text_size) \
221
177
               and (self.text_id == other.text_id) \
222
178
               and (self.parent_id == other.parent_id) \
223
 
               and (self.kind == other.kind)
 
179
               and (self.kind == other.kind) \
 
180
               and (self.text_version == other.text_version) \
 
181
               and (self.entry_version == other.entry_version)
224
182
 
225
183
 
226
184
    def __ne__(self, other):
248
206
 
249
207
 
250
208
 
251
 
class Inventory(XMLMixin):
 
209
class Inventory(object):
252
210
    """Inventory of versioned files in a tree.
253
211
 
254
212
    This describes which file_id is present at each point in the tree,
266
224
    inserted, other than through the Inventory API.
267
225
 
268
226
    >>> inv = Inventory()
269
 
    >>> inv.write_xml(sys.stdout)
270
 
    <inventory>
271
 
    </inventory>
272
227
    >>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
 
228
    InventoryEntry('123-123', 'hello.c', kind='file', parent_id='TREE_ROOT')
273
229
    >>> inv['123-123'].name
274
230
    'hello.c'
275
231
 
284
240
 
285
241
    >>> [x[0] for x in inv.iter_entries()]
286
242
    ['hello.c']
287
 
    
288
 
    >>> inv.write_xml(sys.stdout)
289
 
    <inventory>
290
 
    <entry file_id="123-123" kind="file" name="hello.c" />
291
 
    </inventory>
292
 
 
 
243
    >>> inv = Inventory('TREE_ROOT-12345678-12345678')
 
244
    >>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
 
245
    InventoryEntry('123-123', 'hello.c', kind='file', parent_id='TREE_ROOT-12345678-12345678')
293
246
    """
294
 
    def __init__(self):
 
247
    def __init__(self, root_id=ROOT_ID):
295
248
        """Create or read an inventory.
296
249
 
297
250
        If a working directory is specified, the inventory is read
301
254
        The inventory is created with a default root directory, with
302
255
        an id of None.
303
256
        """
304
 
        self.root = RootEntry(ROOT_ID)
 
257
        # We are letting Branch(init=True) create a unique inventory
 
258
        # root id. Rather than generating a random one here.
 
259
        #if root_id is None:
 
260
        #    root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
 
261
        self.root = RootEntry(root_id)
305
262
        self._byid = {self.root.file_id: self.root}
306
263
 
307
264
 
373
330
 
374
331
        >>> inv = Inventory()
375
332
        >>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
 
333
        InventoryEntry('123', 'foo.c', kind='file', parent_id='TREE_ROOT')
376
334
        >>> '123' in inv
377
335
        True
378
336
        >>> '456' in inv
386
344
 
387
345
        >>> inv = Inventory()
388
346
        >>> inv.add(InventoryEntry('123123', 'hello.c', 'file', ROOT_ID))
 
347
        InventoryEntry('123123', 'hello.c', kind='file', parent_id='TREE_ROOT')
389
348
        >>> inv['123123'].name
390
349
        'hello.c'
391
350
        """
409
368
        """Add entry to inventory.
410
369
 
411
370
        To add  a file to a branch ready to be committed, use Branch.add,
412
 
        which calls this."""
 
371
        which calls this.
 
372
 
 
373
        Returns the new entry object.
 
374
        """
413
375
        if entry.file_id in self._byid:
414
 
            bailout("inventory already contains entry with id {%s}" % entry.file_id)
 
376
            raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
 
377
 
 
378
        if entry.parent_id == ROOT_ID or entry.parent_id is None:
 
379
            entry.parent_id = self.root.file_id
415
380
 
416
381
        try:
417
382
            parent = self._byid[entry.parent_id]
418
383
        except KeyError:
419
 
            bailout("parent_id {%s} not in inventory" % entry.parent_id)
 
384
            raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
420
385
 
421
386
        if parent.children.has_key(entry.name):
422
 
            bailout("%s is already versioned" %
 
387
            raise BzrError("%s is already versioned" %
423
388
                    appendpath(self.id2path(parent.file_id), entry.name))
424
389
 
425
390
        self._byid[entry.file_id] = entry
426
391
        parent.children[entry.name] = entry
 
392
        return entry
427
393
 
428
394
 
429
395
    def add_path(self, relpath, kind, file_id=None):
430
396
        """Add entry from a path.
431
397
 
432
 
        The immediate parent must already be versioned"""
 
398
        The immediate parent must already be versioned.
 
399
 
 
400
        Returns the new entry object."""
 
401
        from bzrlib.branch import gen_file_id
 
402
        
433
403
        parts = bzrlib.osutils.splitpath(relpath)
434
404
        if len(parts) == 0:
435
 
            bailout("cannot re-add root of inventory")
 
405
            raise BzrError("cannot re-add root of inventory")
436
406
 
437
407
        if file_id == None:
438
 
            file_id = bzrlib.branch.gen_file_id(relpath)
439
 
 
440
 
        parent_id = self.path2id(parts[:-1])
441
 
        assert parent_id != None
 
408
            file_id = gen_file_id(relpath)
 
409
 
 
410
        parent_path = parts[:-1]
 
411
        parent_id = self.path2id(parent_path)
 
412
        if parent_id == None:
 
413
            raise NotVersionedError(parent_path)
 
414
 
442
415
        ie = InventoryEntry(file_id, parts[-1],
443
416
                            kind=kind, parent_id=parent_id)
444
417
        return self.add(ie)
449
422
 
450
423
        >>> inv = Inventory()
451
424
        >>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
 
425
        InventoryEntry('123', 'foo.c', kind='file', parent_id='TREE_ROOT')
452
426
        >>> '123' in inv
453
427
        True
454
428
        >>> del inv['123']
470
444
        del self[ie.parent_id].children[ie.name]
471
445
 
472
446
 
473
 
    def to_element(self):
474
 
        """Convert to XML Element"""
475
 
        e = Element('inventory')
476
 
        e.text = '\n'
477
 
        for path, ie in self.iter_entries():
478
 
            e.append(ie.to_element())
479
 
        return e
480
 
    
481
 
 
482
 
    def from_element(cls, elt):
483
 
        """Construct from XML Element
484
 
 
485
 
        >>> inv = Inventory()
486
 
        >>> inv.add(InventoryEntry('foo.c-123981239', 'foo.c', 'file', ROOT_ID))
487
 
        >>> elt = inv.to_element()
488
 
        >>> inv2 = Inventory.from_element(elt)
489
 
        >>> inv2 == inv
490
 
        True
491
 
        """
492
 
        assert elt.tag == 'inventory'
493
 
        o = cls()
494
 
        for e in elt:
495
 
            o.add(InventoryEntry.from_element(e))
496
 
        return o
497
 
        
498
 
    from_element = classmethod(from_element)
499
 
 
500
 
 
501
447
    def __eq__(self, other):
502
448
        """Compare two sets by comparing their contents.
503
449
 
506
452
        >>> i1 == i2
507
453
        True
508
454
        >>> i1.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
 
455
        InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
509
456
        >>> i1 == i2
510
457
        False
511
458
        >>> i2.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
 
459
        InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
512
460
        >>> i1 == i2
513
461
        True
514
462
        """
530
478
        raise ValueError('not hashable')
531
479
 
532
480
 
533
 
 
534
481
    def get_idpath(self, file_id):
535
482
        """Return a list of file_ids for the path to an entry.
536
483
 
544
491
            try:
545
492
                ie = self._byid[file_id]
546
493
            except KeyError:
547
 
                bailout("file_id {%s} not found in inventory" % file_id)
 
494
                raise BzrError("file_id {%s} not found in inventory" % file_id)
548
495
            p.insert(0, ie.file_id)
549
496
            file_id = ie.parent_id
550
497
        return p
554
501
        """Return as a list the path to file_id."""
555
502
 
556
503
        # get all names, skipping root
557
 
        p = [self[fid].name for fid in self.get_idpath(file_id)[1:]]
 
504
        p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
558
505
        return os.sep.join(p)
559
506
            
560
507
 
604
551
 
605
552
        This does not move the working file."""
606
553
        if not is_valid_name(new_name):
607
 
            bailout("not an acceptable filename: %r" % new_name)
 
554
            raise BzrError("not an acceptable filename: %r" % new_name)
608
555
 
609
556
        new_parent = self._byid[new_parent_id]
610
557
        if new_name in new_parent.children:
611
 
            bailout("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
 
558
            raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
612
559
 
613
560
        new_parent_idpath = self.get_idpath(new_parent_id)
614
561
        if file_id in new_parent_idpath:
615
 
            bailout("cannot move directory %r into a subdirectory of itself, %r"
 
562
            raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
616
563
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
617
564
 
618
565
        file_ie = self._byid[file_id]
629
576
 
630
577
 
631
578
 
632
 
_NAME_RE = re.compile(r'^[^/\\]+$')
 
579
_NAME_RE = None
633
580
 
634
581
def is_valid_name(name):
 
582
    global _NAME_RE
 
583
    if _NAME_RE == None:
 
584
        _NAME_RE = re.compile(r'^[^/\\]+$')
 
585
        
635
586
    return bool(_NAME_RE.match(name))