~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Martin Pool
  • Date: 2005-05-20 02:16:01 UTC
  • Revision ID: mbp@sourcefrog.net-20050520021601-d931cce5b66227a3
- bzr diff finds a branch from the first parameter,
  if any are given

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
 
25
33
import bzrlib
26
 
from bzrlib.errors import BzrError, BzrCheckError
27
 
 
28
34
from bzrlib.osutils import uuid, quotefn, splitpath, joinpath, appendpath
29
35
from bzrlib.trace import mutter
30
 
from bzrlib.errors import NotVersionedError
31
 
        
32
36
 
33
 
class InventoryEntry(object):
 
37
class InventoryEntry(XMLMixin):
34
38
    """Description of a versioned file.
35
39
 
36
40
    An InventoryEntry has the following fields, which are also
55
59
    >>> i.path2id('')
56
60
    'TREE_ROOT'
57
61
    >>> i.add(InventoryEntry('123', 'src', 'directory', ROOT_ID))
58
 
    InventoryEntry('123', 'src', kind='directory', parent_id='TREE_ROOT')
59
62
    >>> i.add(InventoryEntry('2323', 'hello.c', 'file', parent_id='123'))
60
 
    InventoryEntry('2323', 'hello.c', kind='file', parent_id='123')
61
63
    >>> for j in i.iter_entries():
62
64
    ...   print j
63
65
    ... 
66
68
    >>> i.add(InventoryEntry('2323', 'bye.c', 'file', '123'))
67
69
    Traceback (most recent call last):
68
70
    ...
69
 
    BzrError: inventory already contains entry with id {2323}
 
71
    BzrError: ('inventory already contains entry with id {2323}', [])
70
72
    >>> i.add(InventoryEntry('2324', 'bye.c', 'file', '123'))
71
 
    InventoryEntry('2324', 'bye.c', kind='file', parent_id='123')
72
73
    >>> 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')
80
79
    >>> i['2326']
81
80
    InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
82
81
    >>> for j in i.iter_entries():
99
98
    # TODO: split InventoryEntry into subclasses for files,
100
99
    # directories, etc etc.
101
100
 
102
 
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
103
 
                 'text_id', 'parent_id', 'children',
104
 
                 'text_version', 'entry_version', ]
105
 
 
106
 
 
 
101
    text_sha1 = None
 
102
    text_size = None
 
103
    
107
104
    def __init__(self, file_id, name, kind, parent_id, text_id=None):
108
105
        """Create an InventoryEntry
109
106
        
119
116
        Traceback (most recent call last):
120
117
        BzrCheckError: InventoryEntry name 'src/hello.c' is invalid
121
118
        """
122
 
        assert isinstance(name, basestring), name
123
119
        if '/' in name or '\\' in name:
124
120
            raise BzrCheckError('InventoryEntry name %r is invalid' % name)
125
121
        
126
 
        self.text_version = None
127
 
        self.entry_version = None
128
 
        self.text_sha1 = None
129
 
        self.text_size = None
130
122
        self.file_id = file_id
131
123
        self.name = name
132
124
        self.kind = kind
166
158
                   self.parent_id))
167
159
 
168
160
    
 
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
 
169
213
    def __eq__(self, other):
170
214
        if not isinstance(other, InventoryEntry):
171
215
            return NotImplemented
176
220
               and (self.text_size == other.text_size) \
177
221
               and (self.text_id == other.text_id) \
178
222
               and (self.parent_id == other.parent_id) \
179
 
               and (self.kind == other.kind) \
180
 
               and (self.text_version == other.text_version) \
181
 
               and (self.entry_version == other.entry_version)
 
223
               and (self.kind == other.kind)
182
224
 
183
225
 
184
226
    def __ne__(self, other):
206
248
 
207
249
 
208
250
 
209
 
class Inventory(object):
 
251
class Inventory(XMLMixin):
210
252
    """Inventory of versioned files in a tree.
211
253
 
212
254
    This describes which file_id is present at each point in the tree,
224
266
    inserted, other than through the Inventory API.
225
267
 
226
268
    >>> inv = Inventory()
 
269
    >>> inv.write_xml(sys.stdout)
 
270
    <inventory>
 
271
    </inventory>
227
272
    >>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
228
 
    InventoryEntry('123-123', 'hello.c', kind='file', parent_id='TREE_ROOT')
229
273
    >>> inv['123-123'].name
230
274
    'hello.c'
231
275
 
240
284
 
241
285
    >>> [x[0] for x in inv.iter_entries()]
242
286
    ['hello.c']
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')
 
287
    
 
288
    >>> inv.write_xml(sys.stdout)
 
289
    <inventory>
 
290
    <entry file_id="123-123" kind="file" name="hello.c" />
 
291
    </inventory>
 
292
 
246
293
    """
247
 
    def __init__(self, root_id=ROOT_ID):
 
294
    def __init__(self):
248
295
        """Create or read an inventory.
249
296
 
250
297
        If a working directory is specified, the inventory is read
254
301
        The inventory is created with a default root directory, with
255
302
        an id of None.
256
303
        """
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)
 
304
        self.root = RootEntry(ROOT_ID)
262
305
        self._byid = {self.root.file_id: self.root}
263
306
 
264
307
 
286
329
            if ie.kind == 'directory':
287
330
                for cn, cie in self.iter_entries(from_dir=ie.file_id):
288
331
                    yield os.path.join(name, cn), cie
289
 
 
290
 
 
291
 
    def entries(self):
292
 
        """Return list of (path, ie) for all entries except the root.
293
 
 
294
 
        This may be faster than iter_entries.
 
332
                    
 
333
 
 
334
 
 
335
    def directories(self):
 
336
        """Return (path, entry) pairs for all directories.
295
337
        """
296
 
        accum = []
297
 
        def descend(dir_ie, dir_path):
298
 
            kids = dir_ie.children.items()
299
 
            kids.sort()
300
 
            for name, ie in kids:
301
 
                child_path = os.path.join(dir_path, name)
302
 
                accum.append((child_path, ie))
 
338
        def descend(parent_ie):
 
339
            parent_name = parent_ie.name
 
340
            yield parent_name, parent_ie
 
341
 
 
342
            # directory children in sorted order
 
343
            dn = []
 
344
            for ie in parent_ie.children.itervalues():
303
345
                if ie.kind == 'directory':
304
 
                    descend(ie, child_path)
305
 
 
306
 
        descend(self.root, '')
307
 
        return accum
308
 
 
309
 
 
310
 
    def directories(self):
311
 
        """Return (path, entry) pairs for all directories, including the root.
312
 
        """
313
 
        accum = []
314
 
        def descend(parent_ie, parent_path):
315
 
            accum.append((parent_path, parent_ie))
 
346
                    dn.append((ie.name, ie))
 
347
            dn.sort()
316
348
            
317
 
            kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
318
 
            kids.sort()
 
349
            for name, child_ie in dn:
 
350
                for sub_name, sub_ie in descend(child_ie):
 
351
                    yield appendpath(parent_name, sub_name), sub_ie
319
352
 
320
 
            for name, child_ie in kids:
321
 
                child_path = os.path.join(parent_path, name)
322
 
                descend(child_ie, child_path)
323
 
        descend(self.root, '')
324
 
        return accum
 
353
        for name, ie in descend(self.root):
 
354
            yield name, ie
325
355
        
326
356
 
327
357
 
330
360
 
331
361
        >>> inv = Inventory()
332
362
        >>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
333
 
        InventoryEntry('123', 'foo.c', kind='file', parent_id='TREE_ROOT')
334
363
        >>> '123' in inv
335
364
        True
336
365
        >>> '456' in inv
344
373
 
345
374
        >>> inv = Inventory()
346
375
        >>> inv.add(InventoryEntry('123123', 'hello.c', 'file', ROOT_ID))
347
 
        InventoryEntry('123123', 'hello.c', kind='file', parent_id='TREE_ROOT')
348
376
        >>> inv['123123'].name
349
377
        'hello.c'
350
378
        """
368
396
        """Add entry to inventory.
369
397
 
370
398
        To add  a file to a branch ready to be committed, use Branch.add,
371
 
        which calls this.
372
 
 
373
 
        Returns the new entry object.
374
 
        """
 
399
        which calls this."""
375
400
        if entry.file_id in self._byid:
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
 
401
            bailout("inventory already contains entry with id {%s}" % entry.file_id)
380
402
 
381
403
        try:
382
404
            parent = self._byid[entry.parent_id]
383
405
        except KeyError:
384
 
            raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
 
406
            bailout("parent_id {%s} not in inventory" % entry.parent_id)
385
407
 
386
408
        if parent.children.has_key(entry.name):
387
 
            raise BzrError("%s is already versioned" %
 
409
            bailout("%s is already versioned" %
388
410
                    appendpath(self.id2path(parent.file_id), entry.name))
389
411
 
390
412
        self._byid[entry.file_id] = entry
391
413
        parent.children[entry.name] = entry
392
 
        return entry
393
414
 
394
415
 
395
416
    def add_path(self, relpath, kind, file_id=None):
396
417
        """Add entry from a path.
397
418
 
398
 
        The immediate parent must already be versioned.
399
 
 
400
 
        Returns the new entry object."""
401
 
        from bzrlib.branch import gen_file_id
402
 
        
 
419
        The immediate parent must already be versioned"""
403
420
        parts = bzrlib.osutils.splitpath(relpath)
404
421
        if len(parts) == 0:
405
 
            raise BzrError("cannot re-add root of inventory")
 
422
            bailout("cannot re-add root of inventory")
406
423
 
407
424
        if file_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
 
 
 
425
            file_id = bzrlib.branch.gen_file_id(relpath)
 
426
 
 
427
        parent_id = self.path2id(parts[:-1])
 
428
        assert parent_id != None
415
429
        ie = InventoryEntry(file_id, parts[-1],
416
430
                            kind=kind, parent_id=parent_id)
417
431
        return self.add(ie)
422
436
 
423
437
        >>> inv = Inventory()
424
438
        >>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
425
 
        InventoryEntry('123', 'foo.c', kind='file', parent_id='TREE_ROOT')
426
439
        >>> '123' in inv
427
440
        True
428
441
        >>> del inv['123']
444
457
        del self[ie.parent_id].children[ie.name]
445
458
 
446
459
 
 
460
    def to_element(self):
 
461
        """Convert to XML Element"""
 
462
        e = Element('inventory')
 
463
        e.text = '\n'
 
464
        for path, ie in self.iter_entries():
 
465
            e.append(ie.to_element())
 
466
        return e
 
467
    
 
468
 
 
469
    def from_element(cls, elt):
 
470
        """Construct from XML Element
 
471
 
 
472
        >>> inv = Inventory()
 
473
        >>> inv.add(InventoryEntry('foo.c-123981239', 'foo.c', 'file', ROOT_ID))
 
474
        >>> elt = inv.to_element()
 
475
        >>> inv2 = Inventory.from_element(elt)
 
476
        >>> inv2 == inv
 
477
        True
 
478
        """
 
479
        assert elt.tag == 'inventory'
 
480
        o = cls()
 
481
        for e in elt:
 
482
            o.add(InventoryEntry.from_element(e))
 
483
        return o
 
484
        
 
485
    from_element = classmethod(from_element)
 
486
 
 
487
 
447
488
    def __eq__(self, other):
448
489
        """Compare two sets by comparing their contents.
449
490
 
452
493
        >>> i1 == i2
453
494
        True
454
495
        >>> i1.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
455
 
        InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
456
496
        >>> i1 == i2
457
497
        False
458
498
        >>> i2.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
459
 
        InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
460
499
        >>> i1 == i2
461
500
        True
462
501
        """
478
517
        raise ValueError('not hashable')
479
518
 
480
519
 
 
520
 
481
521
    def get_idpath(self, file_id):
482
522
        """Return a list of file_ids for the path to an entry.
483
523
 
491
531
            try:
492
532
                ie = self._byid[file_id]
493
533
            except KeyError:
494
 
                raise BzrError("file_id {%s} not found in inventory" % file_id)
 
534
                bailout("file_id {%s} not found in inventory" % file_id)
495
535
            p.insert(0, ie.file_id)
496
536
            file_id = ie.parent_id
497
537
        return p
501
541
        """Return as a list the path to file_id."""
502
542
 
503
543
        # get all names, skipping root
504
 
        p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
 
544
        p = [self[fid].name for fid in self.get_idpath(file_id)[1:]]
505
545
        return os.sep.join(p)
506
546
            
507
547
 
551
591
 
552
592
        This does not move the working file."""
553
593
        if not is_valid_name(new_name):
554
 
            raise BzrError("not an acceptable filename: %r" % new_name)
 
594
            bailout("not an acceptable filename: %r" % new_name)
555
595
 
556
596
        new_parent = self._byid[new_parent_id]
557
597
        if new_name in new_parent.children:
558
 
            raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
 
598
            bailout("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
559
599
 
560
600
        new_parent_idpath = self.get_idpath(new_parent_id)
561
601
        if file_id in new_parent_idpath:
562
 
            raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
 
602
            bailout("cannot move directory %r into a subdirectory of itself, %r"
563
603
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
564
604
 
565
605
        file_ie = self._byid[file_id]
576
616
 
577
617
 
578
618
 
579
 
_NAME_RE = None
 
619
_NAME_RE = re.compile(r'^[^/\\]+$')
580
620
 
581
621
def is_valid_name(name):
582
 
    global _NAME_RE
583
 
    if _NAME_RE == None:
584
 
        _NAME_RE = re.compile(r'^[^/\\]+$')
585
 
        
586
622
    return bool(_NAME_RE.match(name))