/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: Ian Clatworthy
  • Date: 2009-07-13 06:04:28 UTC
  • mfrom: (4527 +trunk)
  • mto: (4527.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 4529.
  • Revision ID: ian.clatworthy@canonical.com-20090713060428-5m6apiywie6aqg6v
merge bzr.dev r4527

Show diffs side-by-side

added added

removed removed

Lines of Context:
27
27
# created, but it's not for now.
28
28
ROOT_ID = "TREE_ROOT"
29
29
 
30
 
from copy import deepcopy
31
 
 
32
30
from bzrlib.lazy_import import lazy_import
33
31
lazy_import(globals(), """
34
32
import collections
 
33
import copy
35
34
import os
36
35
import re
37
36
import tarfile
43
42
    generate_ids,
44
43
    osutils,
45
44
    symbol_versioning,
46
 
    workingtree,
47
45
    )
48
46
""")
49
47
 
714
712
 
715
713
 
716
714
class CommonInventory(object):
717
 
    """Basic inventory logic, defined in terms of primitives like has_id."""
 
715
    """Basic inventory logic, defined in terms of primitives like has_id.
 
716
 
 
717
    An inventory is the metadata about the contents of a tree.
 
718
 
 
719
    This is broadly a map from file_id to entries such as directories, files,
 
720
    symlinks and tree references. Each entry maintains its own metadata like
 
721
    SHA1 and length for files, or children for a directory.
 
722
 
 
723
    Entries can be looked up either by path or by file_id.
 
724
 
 
725
    InventoryEntry objects must not be modified after they are
 
726
    inserted, other than through the Inventory API.
 
727
    """
718
728
 
719
729
    def __contains__(self, file_id):
720
730
        """True if this entry contains a file with given id.
1021
1031
 
1022
1032
 
1023
1033
class Inventory(CommonInventory):
1024
 
    """Inventory of versioned files in a tree.
1025
 
 
1026
 
    This describes which file_id is present at each point in the tree,
1027
 
    and possibly the SHA-1 or other information about the file.
1028
 
    Entries can be looked up either by path or by file_id.
1029
 
 
1030
 
    The inventory represents a typical unix file tree, with
1031
 
    directories containing files and subdirectories.  We never store
1032
 
    the full path to a file, because renaming a directory implicitly
1033
 
    moves all of its contents.  This class internally maintains a
 
1034
    """Mutable dict based in-memory inventory.
 
1035
 
 
1036
    We never store the full path to a file, because renaming a directory
 
1037
    implicitly moves all of its contents.  This class internally maintains a
1034
1038
    lookup tree that allows the children under a directory to be
1035
1039
    returned quickly.
1036
1040
 
1037
 
    InventoryEntry objects must not be modified after they are
1038
 
    inserted, other than through the Inventory API.
1039
 
 
1040
1041
    >>> inv = Inventory()
1041
1042
    >>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
1042
1043
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1043
1044
    >>> inv['123-123'].name
1044
1045
    'hello.c'
1045
1046
 
1046
 
    May be treated as an iterator or set to look up file ids:
 
1047
    Id's may be looked up from paths:
1047
1048
 
1048
 
    >>> bool(inv.path2id('hello.c'))
1049
 
    True
 
1049
    >>> inv.path2id('hello.c')
 
1050
    '123-123'
1050
1051
    >>> '123-123' in inv
1051
1052
    True
1052
1053
 
1053
 
    May also look up by name:
 
1054
    There are iterators over the contents:
1054
1055
 
1055
 
    >>> [x[0] for x in inv.iter_entries()]
 
1056
    >>> [entry[0] for entry in inv.iter_entries()]
1056
1057
    ['', u'hello.c']
1057
 
    >>> inv = Inventory('TREE_ROOT-12345678-12345678')
1058
 
    >>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
1059
 
    Traceback (most recent call last):
1060
 
    BzrError: parent_id {TREE_ROOT} not in inventory
1061
 
    >>> inv.add(InventoryFile('123-123', 'hello.c', 'TREE_ROOT-12345678-12345678'))
1062
 
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None, revision=None)
1063
1058
    """
 
1059
 
1064
1060
    def __init__(self, root_id=ROOT_ID, revision_id=None):
1065
1061
        """Create or read an inventory.
1066
1062
 
1090
1086
    def apply_delta(self, delta):
1091
1087
        """Apply a delta to this inventory.
1092
1088
 
 
1089
        See the inventory developers documentation for the theory behind
 
1090
        inventory deltas.
 
1091
 
1093
1092
        :param delta: A list of changes to apply. After all the changes are
1094
1093
            applied the final inventory must be internally consistent, but it
1095
1094
            is ok to supply changes which, if only half-applied would have an
1126
1125
        """
1127
1126
        # Check that the delta is legal. It would be nice if this could be
1128
1127
        # done within the loops below but it's safer to validate the delta
1129
 
        # before starting to mutate the inventory.
1130
 
        unique_file_ids = set([f for _, _, f, _ in delta])
1131
 
        if len(unique_file_ids) != len(delta):
1132
 
            raise AssertionError("a file-id appears multiple times in %r"
1133
 
                    % (delta,))
1134
 
        del unique_file_ids
 
1128
        # before starting to mutate the inventory, as there isn't a rollback
 
1129
        # facility.
 
1130
        list(_check_delta_unique_ids(_check_delta_unique_new_paths(
 
1131
            _check_delta_unique_old_paths(_check_delta_ids_match_entry(
 
1132
            delta)))))
1135
1133
 
1136
1134
        children = {}
1137
1135
        # Remove all affected items which were in the original inventory,
1166
1164
                replacement.revision = new_entry.revision
1167
1165
                replacement.children = children.pop(replacement.file_id, {})
1168
1166
                new_entry = replacement
1169
 
            self.add(new_entry)
 
1167
            try:
 
1168
                self.add(new_entry)
 
1169
            except AttributeError:
 
1170
                raise errors.InconsistentDelta(new_path, new_entry.file_id,
 
1171
                    "Parent is not a directory.")
1170
1172
        if len(children):
1171
1173
            # Get the parent id that was deleted
1172
1174
            parent_id, children = children.popitem()
1192
1194
 
1193
1195
    def _get_mutable_inventory(self):
1194
1196
        """See CommonInventory._get_mutable_inventory."""
1195
 
        return deepcopy(self)
 
1197
        return copy.deepcopy(self)
1196
1198
 
1197
1199
    def __iter__(self):
1198
1200
        """Iterate over all file-ids."""
1264
1266
            try:
1265
1267
                parent = self._byid[entry.parent_id]
1266
1268
            except KeyError:
1267
 
                raise BzrError("parent_id {%s} not in inventory" %
1268
 
                               entry.parent_id)
1269
 
 
 
1269
                raise errors.InconsistentDelta("<unknown>", entry.parent_id,
 
1270
                    "Parent not in inventory.")
1270
1271
            if entry.name in parent.children:
1271
 
                raise BzrError("%s is already versioned" %
1272
 
                        osutils.pathjoin(self.id2path(parent.file_id),
1273
 
                        entry.name).encode('utf-8'))
 
1272
                raise errors.InconsistentDelta(
 
1273
                    self.id2path(parent.children[entry.name].file_id),
 
1274
                    entry.file_id,
 
1275
                    "Path already versioned")
1274
1276
            parent.children[entry.name] = entry
1275
1277
        return self._add_child(entry)
1276
1278
 
1578
1580
        propagate_caches=False):
1579
1581
        """Create a new CHKInventory by applying inventory_delta to this one.
1580
1582
 
 
1583
        See the inventory developers documentation for the theory behind
 
1584
        inventory deltas.
 
1585
 
1581
1586
        :param inventory_delta: The inventory delta to apply. See
1582
1587
            Inventory.apply_delta for details.
1583
1588
        :param new_revision_id: The revision id of the resulting CHKInventory.
1615
1620
            result.parent_id_basename_to_file_id = None
1616
1621
        result.root_id = self.root_id
1617
1622
        id_to_entry_delta = []
 
1623
        # inventory_delta is only traversed once, so we just update the
 
1624
        # variable.
 
1625
        # Check for repeated file ids
 
1626
        inventory_delta = _check_delta_unique_ids(inventory_delta)
 
1627
        # Repeated old paths
 
1628
        inventory_delta = _check_delta_unique_old_paths(inventory_delta)
 
1629
        # Check for repeated new paths
 
1630
        inventory_delta = _check_delta_unique_new_paths(inventory_delta)
 
1631
        # Check for entries that don't match the fileid
 
1632
        inventory_delta = _check_delta_ids_match_entry(inventory_delta)
 
1633
        # All changed entries need to have their parents be directories.
 
1634
        parents = set()
1618
1635
        for old_path, new_path, file_id, entry in inventory_delta:
1619
1636
            # file id changes
1620
1637
            if new_path == '':
1635
1652
                # Update caches. It's worth doing this whether
1636
1653
                # we're propagating the old caches or not.
1637
1654
                result._path_to_fileid_cache[new_path] = file_id
 
1655
                parents.add(entry.parent_id)
1638
1656
            if old_path is None:
1639
1657
                old_key = None
1640
1658
            else:
1660
1678
        result.id_to_entry.apply_delta(id_to_entry_delta)
1661
1679
        if parent_id_basename_delta:
1662
1680
            result.parent_id_basename_to_file_id.apply_delta(parent_id_basename_delta)
 
1681
        parents.discard(None)
 
1682
        for parent in parents:
 
1683
            try:
 
1684
                if result[parent].kind != 'directory':
 
1685
                    raise errors.InconsistentDelta(result.id2path(parent), parent,
 
1686
                        'Not a directory, but given children')
 
1687
            except errors.NoSuchId:
 
1688
                raise errors.InconsistentDelta("<unknown>", parent,
 
1689
                    "Parent is not present in resulting inventory.")
1663
1690
        return result
1664
1691
 
1665
1692
    @classmethod
2068
2095
        _NAME_RE = re.compile(r'^[^/\\]+$')
2069
2096
 
2070
2097
    return bool(_NAME_RE.match(name))
 
2098
 
 
2099
 
 
2100
def _check_delta_unique_ids(delta):
 
2101
    """Decorate a delta and check that the file ids in it are unique.
 
2102
 
 
2103
    :return: A generator over delta.
 
2104
    """
 
2105
    ids = set()
 
2106
    for item in delta:
 
2107
        length = len(ids) + 1
 
2108
        ids.add(item[2])
 
2109
        if len(ids) != length:
 
2110
            raise errors.InconsistentDelta(item[0] or item[1], item[2],
 
2111
                "repeated file_id")
 
2112
        yield item
 
2113
 
 
2114
 
 
2115
def _check_delta_unique_new_paths(delta):
 
2116
    """Decorate a delta and check that the new paths in it are unique.
 
2117
 
 
2118
    :return: A generator over delta.
 
2119
    """
 
2120
    paths = set()
 
2121
    for item in delta:
 
2122
        length = len(paths) + 1
 
2123
        path = item[1]
 
2124
        if path is not None:
 
2125
            paths.add(path)
 
2126
            if len(paths) != length:
 
2127
                raise errors.InconsistentDelta(path, item[2], "repeated path")
 
2128
        yield item
 
2129
 
 
2130
 
 
2131
def _check_delta_unique_old_paths(delta):
 
2132
    """Decorate a delta and check that the old paths in it are unique.
 
2133
 
 
2134
    :return: A generator over delta.
 
2135
    """
 
2136
    paths = set()
 
2137
    for item in delta:
 
2138
        length = len(paths) + 1
 
2139
        path = item[0]
 
2140
        if path is not None:
 
2141
            paths.add(path)
 
2142
            if len(paths) != length:
 
2143
                raise errors.InconsistentDelta(path, item[2], "repeated path")
 
2144
        yield item
 
2145
 
 
2146
 
 
2147
def _check_delta_ids_match_entry(delta):
 
2148
    """Decorate a delta and check that the ids in it match the entry.file_id.
 
2149
 
 
2150
    :return: A generator over delta.
 
2151
    """
 
2152
    for item in delta:
 
2153
        entry = item[3]
 
2154
        if entry is not None:
 
2155
            if entry.file_id != item[2]:
 
2156
                raise errors.InconsistentDelta(item[0] or item[1], item[2],
 
2157
                    "mismatched id with %r" % entry)
 
2158
        yield item