~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Alexander Belchenko
  • Date: 2007-10-04 05:50:44 UTC
  • mfrom: (2881 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2884.
  • Revision ID: bialix@ukr.net-20071004055044-pb88kgkfayawro8n
merge bzr.dev

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
        """
879
809
    def _forget_tree_state(self):
880
810
        self.reference_revision = None 
881
811
 
 
812
    def _unchanged(self, previous_ie):
 
813
        """See InventoryEntry._unchanged."""
 
814
        compatible = super(TreeReference, self)._unchanged(previous_ie)
 
815
        if self.reference_revision != previous_ie.reference_revision:
 
816
            compatible = False
 
817
        return compatible
 
818
 
882
819
 
883
820
class Inventory(object):
884
821
    """Inventory of versioned files in a tree.
939
876
            self._byid = {}
940
877
        self.revision_id = revision_id
941
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
 
942
942
    def _set_root(self, ie):
943
943
        self.root = ie
944
944
        self._byid = {self.root.file_id: self.root}
1004
1004
                # if we finished all children, pop it off the stack
1005
1005
                stack.pop()
1006
1006
 
1007
 
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None):
 
1007
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
 
1008
        yield_parents=False):
1008
1009
        """Iterate over the entries in a directory first order.
1009
1010
 
1010
1011
        This returns all entries for a directory before returning
1012
1013
        lexicographically sorted order, and is a hybrid between
1013
1014
        depth-first and breadth-first.
1014
1015
 
 
1016
        :param yield_parents: If True, yield the parents from the root leading
 
1017
            down to specific_file_ids that have been requested. This has no
 
1018
            impact if specific_file_ids is None.
1015
1019
        :return: This yields (path, entry) pairs
1016
1020
        """
1017
1021
        if specific_file_ids:
1023
1027
            if self.root is None:
1024
1028
                return
1025
1029
            # Optimize a common case
1026
 
            if specific_file_ids is not None and len(specific_file_ids) == 1:
 
1030
            if (not yield_parents and specific_file_ids is not None and
 
1031
                len(specific_file_ids) == 1):
1027
1032
                file_id = list(specific_file_ids)[0]
1028
1033
                if file_id in self:
1029
1034
                    yield self.id2path(file_id), self[file_id]
1030
1035
                return 
1031
1036
            from_dir = self.root
1032
 
            if (specific_file_ids is None or 
 
1037
            if (specific_file_ids is None or yield_parents or
1033
1038
                self.root.file_id in specific_file_ids):
1034
1039
                yield u'', self.root
1035
1040
        elif isinstance(from_dir, basestring):
1064
1069
                child_relpath = cur_relpath + child_name
1065
1070
 
1066
1071
                if (specific_file_ids is None or 
1067
 
                    child_ie.file_id in specific_file_ids):
 
1072
                    child_ie.file_id in specific_file_ids or
 
1073
                    (yield_parents and child_ie.file_id in parents)):
1068
1074
                    yield child_relpath, child_ie
1069
1075
 
1070
1076
                if child_ie.kind == 'directory':
1371
1377
        This does not move the working file.
1372
1378
        """
1373
1379
        file_id = osutils.safe_file_id(file_id)
 
1380
        new_name = ensure_normalized_name(new_name)
1374
1381
        if not is_valid_name(new_name):
1375
1382
            raise BzrError("not an acceptable filename: %r" % new_name)
1376
1383
 
1418
1425
        file_id = generate_ids.gen_file_id(name)
1419
1426
    else:
1420
1427
        file_id = osutils.safe_file_id(file_id)
1421
 
 
 
1428
    name = ensure_normalized_name(name)
 
1429
    try:
 
1430
        factory = entry_factory[kind]
 
1431
    except KeyError:
 
1432
        raise BzrError("unknown kind %r" % kind)
 
1433
    return factory(file_id, name, parent_id)
 
1434
 
 
1435
 
 
1436
def ensure_normalized_name(name):
 
1437
    """Normalize name.
 
1438
 
 
1439
    :raises InvalidNormalization: When name is not normalized, and cannot be
 
1440
        accessed on this platform by the normalized path.
 
1441
    :return: The NFC/NFKC normalised version of name.
 
1442
    """
1422
1443
    #------- This has been copied to bzrlib.dirstate.DirState.add, please
1423
1444
    # keep them synchronised.
1424
1445
    # we dont import normalized_filename directly because we want to be
1426
1447
    norm_name, can_access = osutils.normalized_filename(name)
1427
1448
    if norm_name != name:
1428
1449
        if can_access:
1429
 
            name = norm_name
 
1450
            return norm_name
1430
1451
        else:
1431
1452
            # TODO: jam 20060701 This would probably be more useful
1432
1453
            #       if the error was raised with the full path
1433
1454
            raise errors.InvalidNormalization(name)
1434
 
 
1435
 
    try:
1436
 
        factory = entry_factory[kind]
1437
 
    except KeyError:
1438
 
        raise BzrError("unknown kind %r" % kind)
1439
 
    return factory(file_id, name, parent_id)
 
1455
    return name
1440
1456
 
1441
1457
 
1442
1458
_NAME_RE = None