~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

Add bzrlib.pyutils, which has get_named_object, a wrapper around __import__.

This is used to replace various ad hoc implementations of the same logic,
notably the version used in registry's _LazyObjectGetter which had a bug when
getting a module without also getting a member.  And of course, this new
function has unit tests, unlike the replaced code.

This also adds a KnownHooksRegistry subclass to provide a more natural home for
some other logic.

I'm not thrilled about the name of the new module or the new functions, but it's
hard to think of good names for such generic functionality.

Show diffs side-by-side

added added

removed removed

Lines of Context:
35
35
import re
36
36
import tarfile
37
37
 
38
 
import bzrlib
39
38
from bzrlib import (
40
39
    chk_map,
41
40
    errors,
42
41
    generate_ids,
43
42
    osutils,
44
 
    symbol_versioning,
45
43
    )
46
44
""")
47
45
 
49
47
    BzrCheckError,
50
48
    BzrError,
51
49
    )
52
 
from bzrlib.symbol_versioning import deprecated_in, deprecated_method
53
50
from bzrlib.trace import mutter
54
51
from bzrlib.static_tuple import StaticTuple
55
52
 
131
128
    RENAMED = 'renamed'
132
129
    MODIFIED_AND_RENAMED = 'modified and renamed'
133
130
 
134
 
    __slots__ = []
 
131
    __slots__ = ['file_id', 'revision', 'parent_id', 'name']
 
132
 
 
133
    # Attributes that all InventoryEntry instances are expected to have, but
 
134
    # that don't vary for all kinds of entry.  (e.g. symlink_target is only
 
135
    # relevant to InventoryLink, so there's no reason to make every
 
136
    # InventoryFile instance allocate space to hold a value for it.)
 
137
    # Attributes that only vary for files: executable, text_sha1, text_size,
 
138
    # text_id
 
139
    executable = False
 
140
    text_sha1 = None
 
141
    text_size = None
 
142
    text_id = None
 
143
    # Attributes that only vary for symlinks: symlink_target
 
144
    symlink_target = None
 
145
    # Attributes that only vary for tree-references: reference_revision
 
146
    reference_revision = None
 
147
 
135
148
 
136
149
    def detect_changes(self, old_entry):
137
150
        """Return a (text_modified, meta_modified) from this to old_entry.
176
189
                    candidates[ie.revision] = ie
177
190
        return candidates
178
191
 
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
192
    def has_text(self):
190
193
        """Return true if the object this entry represents has textual data.
191
194
 
197
200
        """
198
201
        return False
199
202
 
200
 
    def __init__(self, file_id, name, parent_id, text_id=None):
 
203
    def __init__(self, file_id, name, parent_id):
201
204
        """Create an InventoryEntry
202
205
 
203
206
        The filename must be a single component, relative to the
214
217
        """
215
218
        if '/' in name or '\\' in name:
216
219
            raise errors.InvalidEntryName(name=name)
217
 
        self.executable = False
 
220
        self.file_id = file_id
218
221
        self.revision = None
219
 
        self.text_sha1 = None
220
 
        self.text_size = None
221
 
        self.file_id = file_id
222
222
        self.name = name
223
 
        self.text_id = text_id
224
223
        self.parent_id = parent_id
225
 
        self.symlink_target = None
226
 
        self.reference_revision = None
227
224
 
228
225
    def kind_character(self):
229
226
        """Return a short kind indicator useful for appending to names."""
231
228
 
232
229
    known_kinds = ('file', 'directory', 'symlink')
233
230
 
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
231
    def sorted_children(self):
258
232
        return sorted(self.children.items())
259
233
 
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}'
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,
940
836
                if ie.kind == 'directory':
941
837
                    descend(ie, child_path)
942
838
 
943
 
        descend(self.root, u'')
 
839
        if self.root is not None:
 
840
            descend(self.root, u'')
944
841
        return accum
945
842
 
946
843
    def directories(self):
1282
1179
    def add(self, entry):
1283
1180
        """Add entry to inventory.
1284
1181
 
1285
 
        To add  a file to a branch ready to be committed, use Branch.add,
1286
 
        which calls this.
1287
 
 
1288
1182
        :return: entry
1289
1183
        """
1290
1184
        if entry.file_id in self._byid:
1652
1546
            # parent_to_children with at least the tree root.)
1653
1547
            return other
1654
1548
        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
 
1549
        remaining_children = collections.deque(parent_to_children[self.root_id])
1660
1550
        while remaining_children:
1661
1551
            file_id = remaining_children.popleft()
1662
1552
            ie = cache[file_id]
2245
2135
class CHKInventoryDirectory(InventoryDirectory):
2246
2136
    """A directory in an inventory."""
2247
2137
 
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']
 
2138
    __slots__ = ['_children', '_chk_inventory']
2252
2139
 
2253
2140
    def __init__(self, file_id, name, parent_id, chk_inventory):
2254
2141
        # Don't call InventoryDirectory.__init__ - it isn't right for this
2255
2142
        # class.
2256
2143
        InventoryEntry.__init__(self, file_id, name, parent_id)
2257
2144
        self._children = None
2258
 
        self.kind = 'directory'
2259
2145
        self._chk_inventory = chk_inventory
2260
2146
 
2261
2147
    @property