/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: John Arbash Meinel
  • Date: 2011-04-20 14:27:19 UTC
  • mto: This revision was merged to the branch mainline in revision 5837.
  • Revision ID: john@arbash-meinel.com-20110420142719-advs1k5vztqzbrgv
Fix bug #767177. Be more agressive with file.close() calls.

Our test suite gets a number of thread leaks and failures because it happens to get async
SFTPFile.close() calls. (if an SFTPFile closes due to __del__ it is done as an async request,
while if you call SFTPFile.close() it is done as a synchronous request.)
We have a couple other cases, probably. Namely SFTPTransport.get() also does an async
prefetch of the content, so if you don't .read() you'll also leak threads that think they
are doing work that you want.

The biggest change here, though, is using a try/finally in a generator, which is not 
python2.4 compatible.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
31
31
lazy_import(globals(), """
32
32
import collections
33
33
import copy
34
 
import os
35
34
import re
36
35
import tarfile
37
36
 
38
 
import bzrlib
39
37
from bzrlib import (
40
38
    chk_map,
41
39
    errors,
42
40
    generate_ids,
43
41
    osutils,
44
 
    symbol_versioning,
45
42
    )
46
43
""")
47
44
 
48
 
from bzrlib.errors import (
49
 
    BzrCheckError,
50
 
    BzrError,
 
45
from bzrlib import (
 
46
    lazy_regex,
 
47
    trace,
51
48
    )
52
 
from bzrlib.symbol_versioning import deprecated_in, deprecated_method
53
 
from bzrlib.trace import mutter
 
49
 
54
50
from bzrlib.static_tuple import StaticTuple
55
51
 
56
52
 
131
127
    RENAMED = 'renamed'
132
128
    MODIFIED_AND_RENAMED = 'modified and renamed'
133
129
 
134
 
    __slots__ = []
 
130
    __slots__ = ['file_id', 'revision', 'parent_id', 'name']
 
131
 
 
132
    # Attributes that all InventoryEntry instances are expected to have, but
 
133
    # that don't vary for all kinds of entry.  (e.g. symlink_target is only
 
134
    # relevant to InventoryLink, so there's no reason to make every
 
135
    # InventoryFile instance allocate space to hold a value for it.)
 
136
    # Attributes that only vary for files: executable, text_sha1, text_size,
 
137
    # text_id
 
138
    executable = False
 
139
    text_sha1 = None
 
140
    text_size = None
 
141
    text_id = None
 
142
    # Attributes that only vary for symlinks: symlink_target
 
143
    symlink_target = None
 
144
    # Attributes that only vary for tree-references: reference_revision
 
145
    reference_revision = None
 
146
 
135
147
 
136
148
    def detect_changes(self, old_entry):
137
149
        """Return a (text_modified, meta_modified) from this to old_entry.
176
188
                    candidates[ie.revision] = ie
177
189
        return candidates
178
190
 
179
 
    @deprecated_method(deprecated_in((1, 6, 0)))
180
 
    def get_tar_item(self, root, dp, now, tree):
181
 
        """Get a tarfile item and a file stream for its content."""
182
 
        item = tarfile.TarInfo(osutils.pathjoin(root, dp).encode('utf8'))
183
 
        # TODO: would be cool to actually set it to the timestamp of the
184
 
        # revision it was last changed
185
 
        item.mtime = now
186
 
        fileobj = self._put_in_tar(item, tree)
187
 
        return item, fileobj
188
 
 
189
191
    def has_text(self):
190
192
        """Return true if the object this entry represents has textual data.
191
193
 
197
199
        """
198
200
        return False
199
201
 
200
 
    def __init__(self, file_id, name, parent_id, text_id=None):
 
202
    def __init__(self, file_id, name, parent_id):
201
203
        """Create an InventoryEntry
202
204
 
203
205
        The filename must be a single component, relative to the
214
216
        """
215
217
        if '/' in name or '\\' in name:
216
218
            raise errors.InvalidEntryName(name=name)
217
 
        self.executable = False
 
219
        self.file_id = file_id
218
220
        self.revision = None
219
 
        self.text_sha1 = None
220
 
        self.text_size = None
221
 
        self.file_id = file_id
222
221
        self.name = name
223
 
        self.text_id = text_id
224
222
        self.parent_id = parent_id
225
 
        self.symlink_target = None
226
 
        self.reference_revision = None
227
223
 
228
224
    def kind_character(self):
229
225
        """Return a short kind indicator useful for appending to names."""
230
 
        raise BzrError('unknown kind %r' % self.kind)
 
226
        raise errors.BzrError('unknown kind %r' % self.kind)
231
227
 
232
228
    known_kinds = ('file', 'directory', 'symlink')
233
229
 
234
 
    def _put_in_tar(self, item, tree):
235
 
        """populate item for stashing in a tar, and return the content stream.
236
 
 
237
 
        If no content is available, return None.
238
 
        """
239
 
        raise BzrError("don't know how to export {%s} of kind %r" %
240
 
                       (self.file_id, self.kind))
241
 
 
242
 
    @deprecated_method(deprecated_in((1, 6, 0)))
243
 
    def put_on_disk(self, dest, dp, tree):
244
 
        """Create a representation of self on disk in the prefix dest.
245
 
 
246
 
        This is a template method - implement _put_on_disk in subclasses.
247
 
        """
248
 
        fullpath = osutils.pathjoin(dest, dp)
249
 
        self._put_on_disk(fullpath, tree)
250
 
        # mutter("  export {%s} kind %s to %s", self.file_id,
251
 
        #         self.kind, fullpath)
252
 
 
253
 
    def _put_on_disk(self, fullpath, tree):
254
 
        """Put this entry onto disk at fullpath, from tree tree."""
255
 
        raise BzrError("don't know how to export {%s} of kind %r" % (self.file_id, self.kind))
256
 
 
257
230
    def sorted_children(self):
258
231
        return sorted(self.children.items())
259
232
 
276
249
        """
277
250
        if self.parent_id is not None:
278
251
            if not inv.has_id(self.parent_id):
279
 
                raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
280
 
                        % (self.parent_id, rev_id))
 
252
                raise errors.BzrCheckError(
 
253
                    'missing parent {%s} in inventory for revision {%s}' % (
 
254
                        self.parent_id, rev_id))
281
255
        checker._add_entry_to_text_key_references(inv, self)
282
256
        self._check(checker, rev_id)
283
257
 
397
371
        pass
398
372
 
399
373
 
400
 
class RootEntry(InventoryEntry):
401
 
 
402
 
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
403
 
                 'text_id', 'parent_id', 'children', 'executable',
404
 
                 'revision', 'symlink_target', 'reference_revision']
405
 
 
406
 
    def _check(self, checker, rev_id):
407
 
        """See InventoryEntry._check"""
408
 
 
409
 
    def __init__(self, file_id):
410
 
        self.file_id = file_id
411
 
        self.children = {}
412
 
        self.kind = 'directory'
413
 
        self.parent_id = None
414
 
        self.name = u''
415
 
        self.revision = None
416
 
        symbol_versioning.warn('RootEntry is deprecated as of bzr 0.10.'
417
 
                               '  Please use InventoryDirectory instead.',
418
 
                               DeprecationWarning, stacklevel=2)
419
 
 
420
 
    def __eq__(self, other):
421
 
        if not isinstance(other, RootEntry):
422
 
            return NotImplemented
423
 
 
424
 
        return (self.file_id == other.file_id) \
425
 
               and (self.children == other.children)
426
 
 
427
 
 
428
374
class InventoryDirectory(InventoryEntry):
429
375
    """A directory in an inventory."""
430
376
 
431
 
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
432
 
                 'text_id', 'parent_id', 'children', 'executable',
433
 
                 'revision', 'symlink_target', 'reference_revision']
 
377
    __slots__ = ['children']
 
378
 
 
379
    kind = 'directory'
434
380
 
435
381
    def _check(self, checker, rev_id):
436
382
        """See InventoryEntry._check"""
437
 
        if (self.text_sha1 is not None or self.text_size is not None or
438
 
            self.text_id is not None):
439
 
            checker._report_items.append('directory {%s} has text in revision {%s}'
440
 
                                % (self.file_id, rev_id))
441
383
        # In non rich root repositories we do not expect a file graph for the
442
384
        # root.
443
385
        if self.name == '' and not checker.rich_roots:
459
401
    def __init__(self, file_id, name, parent_id):
460
402
        super(InventoryDirectory, self).__init__(file_id, name, parent_id)
461
403
        self.children = {}
462
 
        self.kind = 'directory'
463
404
 
464
405
    def kind_character(self):
465
406
        """See InventoryEntry.kind_character."""
466
407
        return '/'
467
408
 
468
 
    def _put_in_tar(self, item, tree):
469
 
        """See InventoryEntry._put_in_tar."""
470
 
        item.type = tarfile.DIRTYPE
471
 
        fileobj = None
472
 
        item.name += '/'
473
 
        item.size = 0
474
 
        item.mode = 0755
475
 
        return fileobj
476
 
 
477
 
    def _put_on_disk(self, fullpath, tree):
478
 
        """See InventoryEntry._put_on_disk."""
479
 
        os.mkdir(fullpath)
480
 
 
481
409
 
482
410
class InventoryFile(InventoryEntry):
483
411
    """A file in an inventory."""
484
412
 
485
 
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
486
 
                 'text_id', 'parent_id', 'children', 'executable',
487
 
                 'revision', 'symlink_target', 'reference_revision']
 
413
    __slots__ = ['text_sha1', 'text_size', 'text_id', 'executable']
 
414
 
 
415
    kind = 'file'
 
416
 
 
417
    def __init__(self, file_id, name, parent_id):
 
418
        super(InventoryFile, self).__init__(file_id, name, parent_id)
 
419
        self.text_sha1 = None
 
420
        self.text_size = None
 
421
        self.text_id = None
 
422
        self.executable = False
488
423
 
489
424
    def _check(self, checker, tree_revision_id):
490
425
        """See InventoryEntry._check"""
533
468
        """See InventoryEntry.has_text."""
534
469
        return True
535
470
 
536
 
    def __init__(self, file_id, name, parent_id):
537
 
        super(InventoryFile, self).__init__(file_id, name, parent_id)
538
 
        self.kind = 'file'
539
 
 
540
471
    def kind_character(self):
541
472
        """See InventoryEntry.kind_character."""
542
473
        return ''
543
474
 
544
 
    def _put_in_tar(self, item, tree):
545
 
        """See InventoryEntry._put_in_tar."""
546
 
        item.type = tarfile.REGTYPE
547
 
        fileobj = tree.get_file(self.file_id)
548
 
        item.size = self.text_size
549
 
        if tree.is_executable(self.file_id):
550
 
            item.mode = 0755
551
 
        else:
552
 
            item.mode = 0644
553
 
        return fileobj
554
 
 
555
 
    def _put_on_disk(self, fullpath, tree):
556
 
        """See InventoryEntry._put_on_disk."""
557
 
        osutils.pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
558
 
        if tree.is_executable(self.file_id):
559
 
            os.chmod(fullpath, 0755)
560
 
 
561
475
    def _read_tree_state(self, path, work_tree):
562
476
        """See InventoryEntry._read_tree_state."""
563
477
        self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
595
509
class InventoryLink(InventoryEntry):
596
510
    """A file in an inventory."""
597
511
 
598
 
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
599
 
                 'text_id', 'parent_id', 'children', 'executable',
600
 
                 'revision', 'symlink_target', 'reference_revision']
 
512
    __slots__ = ['symlink_target']
 
513
 
 
514
    kind = 'symlink'
 
515
 
 
516
    def __init__(self, file_id, name, parent_id):
 
517
        super(InventoryLink, self).__init__(file_id, name, parent_id)
 
518
        self.symlink_target = None
601
519
 
602
520
    def _check(self, checker, tree_revision_id):
603
521
        """See InventoryEntry._check"""
604
 
        if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
605
 
            checker._report_items.append(
606
 
               'symlink {%s} has text in revision {%s}'
607
 
                    % (self.file_id, tree_revision_id))
608
522
        if self.symlink_target is None:
609
523
            checker._report_items.append(
610
524
                'symlink {%s} has no target in revision {%s}'
625
539
        # FIXME: which _modified field should we use ? RBC 20051003
626
540
        text_modified = (self.symlink_target != old_entry.symlink_target)
627
541
        if text_modified:
628
 
            mutter("    symlink target changed")
 
542
            trace.mutter("    symlink target changed")
629
543
        meta_modified = False
630
544
        return text_modified, meta_modified
631
545
 
648
562
        differ = DiffSymlink(old_tree, new_tree, output_to)
649
563
        return differ.diff_symlink(old_target, new_target)
650
564
 
651
 
    def __init__(self, file_id, name, parent_id):
652
 
        super(InventoryLink, self).__init__(file_id, name, parent_id)
653
 
        self.kind = 'symlink'
654
 
 
655
565
    def kind_character(self):
656
566
        """See InventoryEntry.kind_character."""
657
567
        return ''
658
568
 
659
 
    def _put_in_tar(self, item, tree):
660
 
        """See InventoryEntry._put_in_tar."""
661
 
        item.type = tarfile.SYMTYPE
662
 
        fileobj = None
663
 
        item.size = 0
664
 
        item.mode = 0755
665
 
        item.linkname = self.symlink_target
666
 
        return fileobj
667
 
 
668
 
    def _put_on_disk(self, fullpath, tree):
669
 
        """See InventoryEntry._put_on_disk."""
670
 
        try:
671
 
            os.symlink(self.symlink_target, fullpath)
672
 
        except OSError,e:
673
 
            raise BzrError("Failed to create symlink %r -> %r, error: %s" % (fullpath, self.symlink_target, e))
674
 
 
675
569
    def _read_tree_state(self, path, work_tree):
676
570
        """See InventoryEntry._read_tree_state."""
677
571
        self.symlink_target = work_tree.get_symlink_target(self.file_id)
689
583
 
690
584
class TreeReference(InventoryEntry):
691
585
 
 
586
    __slots__ = ['reference_revision']
 
587
 
692
588
    kind = 'tree-reference'
693
589
 
694
590
    def __init__(self, file_id, name, parent_id, revision=None,
822
718
                # if we finished all children, pop it off the stack
823
719
                stack.pop()
824
720
 
 
721
    def _preload_cache(self):
 
722
        """Populate any caches, we are about to access all items.
 
723
        
 
724
        The default implementation does nothing, because CommonInventory doesn't
 
725
        have a cache.
 
726
        """
 
727
        pass
 
728
    
825
729
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
826
730
        yield_parents=False):
827
731
        """Iterate over the entries in a directory first order.
840
744
            specific_file_ids = set(specific_file_ids)
841
745
        # TODO? Perhaps this should return the from_dir so that the root is
842
746
        # yielded? or maybe an option?
 
747
        if from_dir is None and specific_file_ids is None:
 
748
            # They are iterating from the root, and have not specified any
 
749
            # specific entries to look at. All current callers fully consume the
 
750
            # iterator, so we can safely assume we are accessing all entries
 
751
            self._preload_cache()
843
752
        if from_dir is None:
844
753
            if self.root is None:
845
754
                return
913
822
                    file_id, self[file_id]))
914
823
        return delta
915
824
 
916
 
    def _get_mutable_inventory(self):
917
 
        """Returns a mutable copy of the object.
918
 
 
919
 
        Some inventories are immutable, yet working trees, for example, needs
920
 
        to mutate exisiting inventories instead of creating a new one.
921
 
        """
922
 
        raise NotImplementedError(self._get_mutable_inventory)
923
 
 
924
825
    def make_entry(self, kind, name, parent_id, file_id=None):
925
826
        """Simple thunk to bzrlib.inventory.make_entry."""
926
827
        return make_entry(kind, name, parent_id, file_id)
940
841
                if ie.kind == 'directory':
941
842
                    descend(ie, child_path)
942
843
 
943
 
        descend(self.root, u'')
 
844
        if self.root is not None:
 
845
            descend(self.root, u'')
944
846
        return accum
945
847
 
946
848
    def directories(self):
1223
1125
            other.add(entry.copy())
1224
1126
        return other
1225
1127
 
1226
 
    def _get_mutable_inventory(self):
1227
 
        """See CommonInventory._get_mutable_inventory."""
1228
 
        return copy.deepcopy(self)
1229
 
 
1230
1128
    def __iter__(self):
1231
1129
        """Iterate over all file-ids."""
1232
1130
        return iter(self._byid)
1272
1170
    def _add_child(self, entry):
1273
1171
        """Add an entry to the inventory, without adding it to its parent"""
1274
1172
        if entry.file_id in self._byid:
1275
 
            raise BzrError("inventory already contains entry with id {%s}" %
1276
 
                           entry.file_id)
 
1173
            raise errors.BzrError(
 
1174
                "inventory already contains entry with id {%s}" %
 
1175
                entry.file_id)
1277
1176
        self._byid[entry.file_id] = entry
1278
1177
        for child in getattr(entry, 'children', {}).itervalues():
1279
1178
            self._add_child(child)
1282
1181
    def add(self, entry):
1283
1182
        """Add entry to inventory.
1284
1183
 
1285
 
        To add  a file to a branch ready to be committed, use Branch.add,
1286
 
        which calls this.
1287
 
 
1288
1184
        :return: entry
1289
1185
        """
1290
1186
        if entry.file_id in self._byid:
1446
1342
        """
1447
1343
        new_name = ensure_normalized_name(new_name)
1448
1344
        if not is_valid_name(new_name):
1449
 
            raise BzrError("not an acceptable filename: %r" % new_name)
 
1345
            raise errors.BzrError("not an acceptable filename: %r" % new_name)
1450
1346
 
1451
1347
        new_parent = self._byid[new_parent_id]
1452
1348
        if new_name in new_parent.children:
1453
 
            raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
 
1349
            raise errors.BzrError("%r already exists in %r" %
 
1350
                (new_name, self.id2path(new_parent_id)))
1454
1351
 
1455
1352
        new_parent_idpath = self.get_idpath(new_parent_id)
1456
1353
        if file_id in new_parent_idpath:
1457
 
            raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
 
1354
            raise errors.BzrError(
 
1355
                "cannot move directory %r into a subdirectory of itself, %r"
1458
1356
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
1459
1357
 
1460
1358
        file_ie = self._byid[file_id]
1496
1394
    def __init__(self, search_key_name):
1497
1395
        CommonInventory.__init__(self)
1498
1396
        self._fileid_to_entry_cache = {}
 
1397
        self._fully_cached = False
1499
1398
        self._path_to_fileid_cache = {}
1500
1399
        self._search_key_name = search_key_name
1501
1400
        self.root_id = None
1652
1551
            # parent_to_children with at least the tree root.)
1653
1552
            return other
1654
1553
        cache = self._fileid_to_entry_cache
1655
 
        try:
1656
 
            remaining_children = collections.deque(parent_to_children[self.root_id])
1657
 
        except:
1658
 
            import pdb; pdb.set_trace()
1659
 
            raise
 
1554
        remaining_children = collections.deque(parent_to_children[self.root_id])
1660
1555
        while remaining_children:
1661
1556
            file_id = remaining_children.popleft()
1662
1557
            ie = cache[file_id]
1712
1607
        self._fileid_to_entry_cache[result.file_id] = result
1713
1608
        return result
1714
1609
 
1715
 
    def _get_mutable_inventory(self):
1716
 
        """See CommonInventory._get_mutable_inventory."""
1717
 
        entries = self.iter_entries()
1718
 
        inv = Inventory(None, self.revision_id)
1719
 
        for path, inv_entry in entries:
1720
 
            inv.add(inv_entry.copy())
1721
 
        return inv
1722
 
 
1723
1610
    def create_by_apply_delta(self, inventory_delta, new_revision_id,
1724
1611
        propagate_caches=False):
1725
1612
        """Create a new CHKInventory by applying inventory_delta to this one.
2066
1953
 
2067
1954
    def iter_just_entries(self):
2068
1955
        """Iterate over all entries.
2069
 
        
 
1956
 
2070
1957
        Unlike iter_entries(), just the entries are returned (not (path, ie))
2071
1958
        and the order of entries is undefined.
2072
1959
 
2080
1967
                self._fileid_to_entry_cache[file_id] = ie
2081
1968
            yield ie
2082
1969
 
 
1970
    def _preload_cache(self):
 
1971
        """Make sure all file-ids are in _fileid_to_entry_cache"""
 
1972
        if self._fully_cached:
 
1973
            return # No need to do it again
 
1974
        # The optimal sort order is to use iteritems() directly
 
1975
        cache = self._fileid_to_entry_cache
 
1976
        for key, entry in self.id_to_entry.iteritems():
 
1977
            file_id = key[0]
 
1978
            if file_id not in cache:
 
1979
                ie = self._bytes_to_entry(entry)
 
1980
                cache[file_id] = ie
 
1981
            else:
 
1982
                ie = cache[file_id]
 
1983
        last_parent_id = last_parent_ie = None
 
1984
        pid_items = self.parent_id_basename_to_file_id.iteritems()
 
1985
        for key, child_file_id in pid_items:
 
1986
            if key == ('', ''): # This is the root
 
1987
                if child_file_id != self.root_id:
 
1988
                    raise ValueError('Data inconsistency detected.'
 
1989
                        ' We expected data with key ("","") to match'
 
1990
                        ' the root id, but %s != %s'
 
1991
                        % (child_file_id, self.root_id))
 
1992
                continue
 
1993
            parent_id, basename = key
 
1994
            ie = cache[child_file_id]
 
1995
            if parent_id == last_parent_id:
 
1996
                parent_ie = last_parent_ie
 
1997
            else:
 
1998
                parent_ie = cache[parent_id]
 
1999
            if parent_ie.kind != 'directory':
 
2000
                raise ValueError('Data inconsistency detected.'
 
2001
                    ' An entry in the parent_id_basename_to_file_id map'
 
2002
                    ' has parent_id {%s} but the kind of that object'
 
2003
                    ' is %r not "directory"' % (parent_id, parent_ie.kind))
 
2004
            if parent_ie._children is None:
 
2005
                parent_ie._children = {}
 
2006
            basename = basename.decode('utf-8')
 
2007
            if basename in parent_ie._children:
 
2008
                existing_ie = parent_ie._children[basename]
 
2009
                if existing_ie != ie:
 
2010
                    raise ValueError('Data inconsistency detected.'
 
2011
                        ' Two entries with basename %r were found'
 
2012
                        ' in the parent entry {%s}'
 
2013
                        % (basename, parent_id))
 
2014
            if basename != ie.name:
 
2015
                raise ValueError('Data inconsistency detected.'
 
2016
                    ' In the parent_id_basename_to_file_id map, file_id'
 
2017
                    ' {%s} is listed as having basename %r, but in the'
 
2018
                    ' id_to_entry map it is %r'
 
2019
                    % (child_file_id, basename, ie.name))
 
2020
            parent_ie._children[basename] = ie
 
2021
        self._fully_cached = True
 
2022
 
2083
2023
    def iter_changes(self, basis):
2084
2024
        """Generate a Tree.iter_changes change list between this and basis.
2085
2025
 
2245
2185
class CHKInventoryDirectory(InventoryDirectory):
2246
2186
    """A directory in an inventory."""
2247
2187
 
2248
 
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
2249
 
                 'text_id', 'parent_id', '_children', 'executable',
2250
 
                 'revision', 'symlink_target', 'reference_revision',
2251
 
                 '_chk_inventory']
 
2188
    __slots__ = ['_children', '_chk_inventory']
2252
2189
 
2253
2190
    def __init__(self, file_id, name, parent_id, chk_inventory):
2254
2191
        # Don't call InventoryDirectory.__init__ - it isn't right for this
2255
2192
        # class.
2256
2193
        InventoryEntry.__init__(self, file_id, name, parent_id)
2257
2194
        self._children = None
2258
 
        self.kind = 'directory'
2259
2195
        self._chk_inventory = chk_inventory
2260
2196
 
2261
2197
    @property
2346
2282
    return name
2347
2283
 
2348
2284
 
2349
 
_NAME_RE = None
 
2285
_NAME_RE = lazy_regex.lazy_compile(r'^[^/\\]+$')
2350
2286
 
2351
2287
def is_valid_name(name):
2352
 
    global _NAME_RE
2353
 
    if _NAME_RE is None:
2354
 
        _NAME_RE = re.compile(r'^[^/\\]+$')
2355
 
 
2356
2288
    return bool(_NAME_RE.match(name))
2357
2289
 
2358
2290
 
2448
2380
            raise errors.InconsistentDelta(new_path, item[1],
2449
2381
                "new_path with no entry")
2450
2382
        yield item
 
2383
 
 
2384
 
 
2385
def mutable_inventory_from_tree(tree):
 
2386
    """Create a new inventory that has the same contents as a specified tree.
 
2387
 
 
2388
    :param tree: Revision tree to create inventory from
 
2389
    """
 
2390
    entries = tree.iter_entries_by_dir()
 
2391
    inv = Inventory(None, tree.get_revision_id())
 
2392
    for path, inv_entry in entries:
 
2393
        inv.add(inv_entry.copy())
 
2394
    return inv