~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Martin Pool
  • Date: 2007-10-10 00:21:57 UTC
  • mfrom: (2900 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2901.
  • Revision ID: mbp@sourcefrog.net-20071010002157-utci0x44m2w47wgd
merge news

Show diffs side-by-side

added added

removed removed

Lines of Context:
432
432
                   self.parent_id,
433
433
                   self.revision))
434
434
 
435
 
    def snapshot(self, revision, path, previous_entries,
436
 
                 work_tree, commit_builder):
437
 
        """Make a snapshot of this entry which may or may not have changed.
438
 
        
439
 
        This means that all its fields are populated, that it has its
440
 
        text stored in the text store or weave.
441
 
 
442
 
        :return: True if anything was recorded
443
 
        """
444
 
        # cannot be unchanged unless there is only one parent file rev.
445
 
        self._read_tree_state(path, work_tree)
446
 
        if len(previous_entries) == 1:
447
 
            parent_ie = previous_entries.values()[0]
448
 
            if self._unchanged(parent_ie):
449
 
                self.revision = parent_ie.revision
450
 
                return False
451
 
        self.revision = revision
452
 
        return self._snapshot_text(previous_entries, work_tree, commit_builder)
453
 
 
454
 
    def _snapshot_text(self, file_parents, work_tree, commit_builder): 
455
 
        """Record the 'text' of this entry, whatever form that takes.
456
 
 
457
 
        :return: True if anything was recorded
458
 
        """
459
 
        raise NotImplementedError(self._snapshot_text)
460
 
 
461
435
    def __eq__(self, other):
462
436
        if not isinstance(other, InventoryEntry):
463
437
            return NotImplemented
582
556
        """See InventoryEntry._put_on_disk."""
583
557
        os.mkdir(fullpath)
584
558
 
585
 
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
586
 
        """See InventoryEntry._snapshot_text."""
587
 
        commit_builder.modified_directory(self.file_id, file_parents)
588
 
        return True
589
 
 
590
559
 
591
560
class InventoryFile(InventoryEntry):
592
561
    """A file in an inventory."""
716
685
    def _forget_tree_state(self):
717
686
        self.text_sha1 = None
718
687
 
719
 
    def snapshot(self, revision, path, previous_entries,
720
 
                 work_tree, commit_builder):
721
 
        """See InventoryEntry.snapshot."""
722
 
        # Note: We use a custom implementation of this method for files
723
 
        # because it's a performance critical part of commit.
724
 
 
725
 
        # If this is the initial commit for this file, we know the sha is
726
 
        # coming later so skip calculating it now (in _read_tree_state())
727
 
        if len(previous_entries) == 0:
728
 
            self.executable = work_tree.is_executable(self.file_id, path=path)
729
 
        else:
730
 
            self._read_tree_state(path, work_tree)
731
 
 
732
 
        # If nothing is changed from the sole parent, there's nothing to do
733
 
        if len(previous_entries) == 1:
734
 
            parent_ie = previous_entries.values()[0]
735
 
            if self._unchanged(parent_ie):
736
 
                self.revision = parent_ie.revision
737
 
                return False
738
 
 
739
 
        # Add the file to the repository
740
 
        self.revision = revision
741
 
        def get_content_byte_lines():
742
 
            return work_tree.get_file(self.file_id, path).readlines()
743
 
        self.text_sha1, self.text_size = commit_builder.modified_file_text(
744
 
            self.file_id, previous_entries, get_content_byte_lines,
745
 
            self.text_sha1, self.text_size)
746
 
        return True
747
 
 
748
688
    def _unchanged(self, previous_ie):
749
689
        """See InventoryEntry._unchanged."""
750
690
        compatible = super(InventoryFile, self)._unchanged(previous_ie)
845
785
            compatible = False
846
786
        return compatible
847
787
 
848
 
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
849
 
        """See InventoryEntry._snapshot_text."""
850
 
        commit_builder.modified_link(
851
 
            self.file_id, file_parents, self.symlink_target)
852
 
        return True
853
 
 
854
788
 
855
789
class TreeReference(InventoryEntry):
856
790
    
866
800
        return TreeReference(self.file_id, self.name, self.parent_id,
867
801
                             self.revision, self.reference_revision)
868
802
 
869
 
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
870
 
        commit_builder.modified_reference(self.file_id, file_parents)
871
 
        return True
872
 
 
873
803
    def _read_tree_state(self, path, work_tree):
874
804
        """Populate fields in the inventory entry from the given tree.
875
805
        """
946
876
            self._byid = {}
947
877
        self.revision_id = revision_id
948
878
 
 
879
    def __repr__(self):
 
880
        return "<Inventory object at %x, contents=%r>" % (id(self), self._byid)
 
881
 
 
882
    def apply_delta(self, delta):
 
883
        """Apply a delta to this inventory.
 
884
 
 
885
        :param delta: A list of changes to apply. After all the changes are
 
886
            applied the final inventory must be internally consistent, but it
 
887
            is ok to supply changes which, if only half-applied would have an
 
888
            invalid result - such as supplying two changes which rename two
 
889
            files, 'A' and 'B' with each other : [('A', 'B', 'A-id', a_entry),
 
890
            ('B', 'A', 'B-id', b_entry)].
 
891
 
 
892
            Each change is a tuple, of the form (old_path, new_path, file_id,
 
893
            new_entry).
 
894
            
 
895
            When new_path is None, the change indicates the removal of an entry
 
896
            from the inventory and new_entry will be ignored (using None is
 
897
            appropriate). If new_path is not None, then new_entry must be an
 
898
            InventoryEntry instance, which will be incorporated into the
 
899
            inventory (and replace any existing entry with the same file id).
 
900
            
 
901
            When old_path is None, the change indicates the addition of
 
902
            a new entry to the inventory.
 
903
            
 
904
            When neither new_path nor old_path are None, the change is a
 
905
            modification to an entry, such as a rename, reparent, kind change
 
906
            etc. 
 
907
 
 
908
            The children attribute of new_entry is ignored. This is because
 
909
            this method preserves children automatically across alterations to
 
910
            the parent of the children, and cases where the parent id of a
 
911
            child is changing require the child to be passed in as a separate
 
912
            change regardless. E.g. in the recursive deletion of a directory -
 
913
            the directory's children must be included in the delta, or the
 
914
            final inventory will be invalid.
 
915
        """
 
916
        children = {}
 
917
        # Remove all affected items which were in the original inventory,
 
918
        # starting with the longest paths, thus ensuring parents are examined
 
919
        # after their children, which means that everything we examine has no
 
920
        # modified children remaining by the time we examine it.
 
921
        for old_path, file_id in sorted(((op, f) for op, np, f, e in delta
 
922
                                        if op is not None), reverse=True):
 
923
            if file_id not in self:
 
924
                # adds come later
 
925
                continue
 
926
            # Preserve unaltered children of file_id for later reinsertion.
 
927
            children[file_id] = getattr(self[file_id], 'children', {})
 
928
            # Remove file_id and the unaltered children. If file_id is not
 
929
            # being deleted it will be reinserted back later.
 
930
            self.remove_recursive_id(file_id)
 
931
        # Insert all affected which should be in the new inventory, reattaching
 
932
        # their children if they had any. This is done from shortest path to
 
933
        # longest, ensuring that items which were modified and whose parents in
 
934
        # the resulting inventory were also modified, are inserted after their
 
935
        # parents.
 
936
        for new_path, new_entry in sorted((np, e) for op, np, f, e in
 
937
                                          delta if np is not None):
 
938
            if new_entry.kind == 'directory':
 
939
                new_entry.children = children.get(new_entry.file_id, {})
 
940
            self.add(new_entry)
 
941
 
949
942
    def _set_root(self, ie):
950
943
        self.root = ie
951
944
        self._byid = {self.root.file_id: self.root}
1025
1018
            impact if specific_file_ids is None.
1026
1019
        :return: This yields (path, entry) pairs
1027
1020
        """
1028
 
        if specific_file_ids:
1029
 
            safe = osutils.safe_file_id
1030
 
            specific_file_ids = set(safe(fid) for fid in specific_file_ids)
 
1021
        if specific_file_ids and not isinstance(specific_file_ids, set):
 
1022
            specific_file_ids = set(specific_file_ids)
1031
1023
        # TODO? Perhaps this should return the from_dir so that the root is
1032
1024
        # yielded? or maybe an option?
1033
1025
        if from_dir is None:
1134
1126
        >>> '456' in inv
1135
1127
        False
1136
1128
        """
1137
 
        file_id = osutils.safe_file_id(file_id)
1138
1129
        return (file_id in self._byid)
1139
1130
 
1140
1131
    def __getitem__(self, file_id):
1146
1137
        >>> inv['123123'].name
1147
1138
        'hello.c'
1148
1139
        """
1149
 
        file_id = osutils.safe_file_id(file_id)
1150
1140
        try:
1151
1141
            return self._byid[file_id]
1152
1142
        except KeyError:
1154
1144
            raise errors.NoSuchId(self, file_id)
1155
1145
 
1156
1146
    def get_file_kind(self, file_id):
1157
 
        file_id = osutils.safe_file_id(file_id)
1158
1147
        return self._byid[file_id].kind
1159
1148
 
1160
1149
    def get_child(self, parent_id, filename):
1161
 
        parent_id = osutils.safe_file_id(parent_id)
1162
1150
        return self[parent_id].children.get(filename)
1163
1151
 
1164
1152
    def _add_child(self, entry):
1212
1200
        if len(parts) == 0:
1213
1201
            if file_id is None:
1214
1202
                file_id = generate_ids.gen_root_id()
1215
 
            else:
1216
 
                file_id = osutils.safe_file_id(file_id)
1217
1203
            self.root = InventoryDirectory(file_id, '', None)
1218
1204
            self._byid = {self.root.file_id: self.root}
1219
1205
            return self.root
1237
1223
        >>> '123' in inv
1238
1224
        False
1239
1225
        """
1240
 
        file_id = osutils.safe_file_id(file_id)
1241
1226
        ie = self[file_id]
1242
1227
 
1243
1228
        assert ie.parent_id is None or \
1276
1261
 
1277
1262
    def _iter_file_id_parents(self, file_id):
1278
1263
        """Yield the parents of file_id up to the root."""
1279
 
        file_id = osutils.safe_file_id(file_id)
1280
1264
        while file_id is not None:
1281
1265
            try:
1282
1266
                ie = self._byid[file_id]
1293
1277
        is equal to the depth of the file in the tree, counting the
1294
1278
        root directory as depth 1.
1295
1279
        """
1296
 
        file_id = osutils.safe_file_id(file_id)
1297
1280
        p = []
1298
1281
        for parent in self._iter_file_id_parents(file_id):
1299
1282
            p.insert(0, parent.file_id)
1308
1291
        >>> print i.id2path('foo-id')
1309
1292
        src/foo.c
1310
1293
        """
1311
 
        file_id = osutils.safe_file_id(file_id)
1312
1294
        # get all names, skipping root
1313
1295
        return '/'.join(reversed(
1314
1296
            [parent.name for parent in 
1352
1334
        return bool(self.path2id(names))
1353
1335
 
1354
1336
    def has_id(self, file_id):
1355
 
        file_id = osutils.safe_file_id(file_id)
1356
1337
        return (file_id in self._byid)
1357
1338
 
1358
1339
    def remove_recursive_id(self, file_id):
1360
1341
        
1361
1342
        :param file_id: A file_id to remove.
1362
1343
        """
1363
 
        file_id = osutils.safe_file_id(file_id)
1364
1344
        to_find_delete = [self._byid[file_id]]
1365
1345
        to_delete = []
1366
1346
        while to_find_delete:
1383
1363
 
1384
1364
        This does not move the working file.
1385
1365
        """
1386
 
        file_id = osutils.safe_file_id(file_id)
1387
1366
        new_name = ensure_normalized_name(new_name)
1388
1367
        if not is_valid_name(new_name):
1389
1368
            raise BzrError("not an acceptable filename: %r" % new_name)
1409
1388
        file_ie.parent_id = new_parent_id
1410
1389
 
1411
1390
    def is_root(self, file_id):
1412
 
        file_id = osutils.safe_file_id(file_id)
1413
1391
        return self.root is not None and file_id == self.root.file_id
1414
1392
 
1415
1393
 
1430
1408
    """
1431
1409
    if file_id is None:
1432
1410
        file_id = generate_ids.gen_file_id(name)
1433
 
    else:
1434
 
        file_id = osutils.safe_file_id(file_id)
1435
1411
    name = ensure_normalized_name(name)
1436
1412
    try:
1437
1413
        factory = entry_factory[kind]