~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Rory Yorke
  • Date: 2010-10-20 14:38:53 UTC
  • mto: This revision was merged to the branch mainline in revision 5519.
  • Revision ID: rory.yorke@gmail.com-20101020143853-9kfd2ldcjfroh8jw
Show missing files in bzr status (bug 134168).

"bzr status" will now show missing files, that is, those added with "bzr
add" and then removed by non bzr means (e.g., rm).

Blackbox tests were added for this case, and tests were also added to
test_delta, since the implementation change is in bzrlib.delta.

Might also affect bug 189709.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005-2010 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
34
35
import re
35
36
import tarfile
36
37
 
42
43
    )
43
44
""")
44
45
 
45
 
from bzrlib import (
46
 
    lazy_regex,
47
 
    trace,
 
46
from bzrlib.errors import (
 
47
    BzrCheckError,
 
48
    BzrError,
48
49
    )
49
 
 
 
50
from bzrlib.trace import mutter
50
51
from bzrlib.static_tuple import StaticTuple
51
52
 
52
53
 
223
224
 
224
225
    def kind_character(self):
225
226
        """Return a short kind indicator useful for appending to names."""
226
 
        raise errors.BzrError('unknown kind %r' % self.kind)
 
227
        raise BzrError('unknown kind %r' % self.kind)
227
228
 
228
229
    known_kinds = ('file', 'directory', 'symlink')
229
230
 
249
250
        """
250
251
        if self.parent_id is not None:
251
252
            if not inv.has_id(self.parent_id):
252
 
                raise errors.BzrCheckError(
253
 
                    'missing parent {%s} in inventory for revision {%s}' % (
254
 
                        self.parent_id, rev_id))
 
253
                raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
 
254
                        % (self.parent_id, rev_id))
255
255
        checker._add_entry_to_text_key_references(inv, self)
256
256
        self._check(checker, rev_id)
257
257
 
539
539
        # FIXME: which _modified field should we use ? RBC 20051003
540
540
        text_modified = (self.symlink_target != old_entry.symlink_target)
541
541
        if text_modified:
542
 
            trace.mutter("    symlink target changed")
 
542
            mutter("    symlink target changed")
543
543
        meta_modified = False
544
544
        return text_modified, meta_modified
545
545
 
718
718
                # if we finished all children, pop it off the stack
719
719
                stack.pop()
720
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
 
    
729
721
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
730
722
        yield_parents=False):
731
723
        """Iterate over the entries in a directory first order.
744
736
            specific_file_ids = set(specific_file_ids)
745
737
        # TODO? Perhaps this should return the from_dir so that the root is
746
738
        # 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()
752
739
        if from_dir is None:
753
740
            if self.root is None:
754
741
                return
822
809
                    file_id, self[file_id]))
823
810
        return delta
824
811
 
 
812
    def _get_mutable_inventory(self):
 
813
        """Returns a mutable copy of the object.
 
814
 
 
815
        Some inventories are immutable, yet working trees, for example, needs
 
816
        to mutate exisiting inventories instead of creating a new one.
 
817
        """
 
818
        raise NotImplementedError(self._get_mutable_inventory)
 
819
 
825
820
    def make_entry(self, kind, name, parent_id, file_id=None):
826
821
        """Simple thunk to bzrlib.inventory.make_entry."""
827
822
        return make_entry(kind, name, parent_id, file_id)
1125
1120
            other.add(entry.copy())
1126
1121
        return other
1127
1122
 
 
1123
    def _get_mutable_inventory(self):
 
1124
        """See CommonInventory._get_mutable_inventory."""
 
1125
        return copy.deepcopy(self)
 
1126
 
1128
1127
    def __iter__(self):
1129
1128
        """Iterate over all file-ids."""
1130
1129
        return iter(self._byid)
1170
1169
    def _add_child(self, entry):
1171
1170
        """Add an entry to the inventory, without adding it to its parent"""
1172
1171
        if entry.file_id in self._byid:
1173
 
            raise errors.BzrError(
1174
 
                "inventory already contains entry with id {%s}" %
1175
 
                entry.file_id)
 
1172
            raise BzrError("inventory already contains entry with id {%s}" %
 
1173
                           entry.file_id)
1176
1174
        self._byid[entry.file_id] = entry
1177
1175
        for child in getattr(entry, 'children', {}).itervalues():
1178
1176
            self._add_child(child)
1342
1340
        """
1343
1341
        new_name = ensure_normalized_name(new_name)
1344
1342
        if not is_valid_name(new_name):
1345
 
            raise errors.BzrError("not an acceptable filename: %r" % new_name)
 
1343
            raise BzrError("not an acceptable filename: %r" % new_name)
1346
1344
 
1347
1345
        new_parent = self._byid[new_parent_id]
1348
1346
        if new_name in new_parent.children:
1349
 
            raise errors.BzrError("%r already exists in %r" %
1350
 
                (new_name, self.id2path(new_parent_id)))
 
1347
            raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
1351
1348
 
1352
1349
        new_parent_idpath = self.get_idpath(new_parent_id)
1353
1350
        if file_id in new_parent_idpath:
1354
 
            raise errors.BzrError(
1355
 
                "cannot move directory %r into a subdirectory of itself, %r"
 
1351
            raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
1356
1352
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
1357
1353
 
1358
1354
        file_ie = self._byid[file_id]
1394
1390
    def __init__(self, search_key_name):
1395
1391
        CommonInventory.__init__(self)
1396
1392
        self._fileid_to_entry_cache = {}
1397
 
        self._fully_cached = False
1398
1393
        self._path_to_fileid_cache = {}
1399
1394
        self._search_key_name = search_key_name
1400
1395
        self.root_id = None
1607
1602
        self._fileid_to_entry_cache[result.file_id] = result
1608
1603
        return result
1609
1604
 
 
1605
    def _get_mutable_inventory(self):
 
1606
        """See CommonInventory._get_mutable_inventory."""
 
1607
        entries = self.iter_entries()
 
1608
        inv = Inventory(None, self.revision_id)
 
1609
        for path, inv_entry in entries:
 
1610
            inv.add(inv_entry.copy())
 
1611
        return inv
 
1612
 
1610
1613
    def create_by_apply_delta(self, inventory_delta, new_revision_id,
1611
1614
        propagate_caches=False):
1612
1615
        """Create a new CHKInventory by applying inventory_delta to this one.
1953
1956
 
1954
1957
    def iter_just_entries(self):
1955
1958
        """Iterate over all entries.
1956
 
 
 
1959
        
1957
1960
        Unlike iter_entries(), just the entries are returned (not (path, ie))
1958
1961
        and the order of entries is undefined.
1959
1962
 
1967
1970
                self._fileid_to_entry_cache[file_id] = ie
1968
1971
            yield ie
1969
1972
 
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
 
 
2023
1973
    def iter_changes(self, basis):
2024
1974
        """Generate a Tree.iter_changes change list between this and basis.
2025
1975
 
2282
2232
    return name
2283
2233
 
2284
2234
 
2285
 
_NAME_RE = lazy_regex.lazy_compile(r'^[^/\\]+$')
 
2235
_NAME_RE = None
2286
2236
 
2287
2237
def is_valid_name(name):
 
2238
    global _NAME_RE
 
2239
    if _NAME_RE is None:
 
2240
        _NAME_RE = re.compile(r'^[^/\\]+$')
 
2241
 
2288
2242
    return bool(_NAME_RE.match(name))
2289
2243
 
2290
2244
 
2380
2334
            raise errors.InconsistentDelta(new_path, item[1],
2381
2335
                "new_path with no entry")
2382
2336
        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