31
31
lazy_import(globals(), """
37
39
from bzrlib import (
48
from bzrlib.errors import (
52
from bzrlib.symbol_versioning import deprecated_in, deprecated_method
53
from bzrlib.trace import mutter
50
54
from bzrlib.static_tuple import StaticTuple
51
from bzrlib.symbol_versioning import (
57
57
class InventoryEntry(object):
104
104
InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
105
105
>>> i.path2id('src/wibble')
107
109
>>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
108
110
InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None, revision=None)
129
131
RENAMED = 'renamed'
130
132
MODIFIED_AND_RENAMED = 'modified and renamed'
132
__slots__ = ['file_id', 'revision', 'parent_id', 'name']
134
# Attributes that all InventoryEntry instances are expected to have, but
135
# that don't vary for all kinds of entry. (e.g. symlink_target is only
136
# relevant to InventoryLink, so there's no reason to make every
137
# InventoryFile instance allocate space to hold a value for it.)
138
# Attributes that only vary for files: executable, text_sha1, text_size,
144
# Attributes that only vary for symlinks: symlink_target
145
symlink_target = None
146
# Attributes that only vary for tree-references: reference_revision
147
reference_revision = None
150
136
def detect_changes(self, old_entry):
151
137
"""Return a (text_modified, meta_modified) from this to old_entry.
190
176
candidates[ie.revision] = ie
191
177
return candidates
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
186
fileobj = self._put_in_tar(item, tree)
193
189
def has_text(self):
194
190
"""Return true if the object this entry represents has textual data.
204
def __init__(self, file_id, name, parent_id):
200
def __init__(self, file_id, name, parent_id, text_id=None):
205
201
"""Create an InventoryEntry
207
203
The filename must be a single component, relative to the
219
215
if '/' in name or '\\' in name:
220
216
raise errors.InvalidEntryName(name=name)
217
self.executable = False
219
self.text_sha1 = None
220
self.text_size = None
221
221
self.file_id = file_id
223
self.text_id = text_id
224
224
self.parent_id = parent_id
225
self.symlink_target = None
226
self.reference_revision = None
226
228
def kind_character(self):
227
229
"""Return a short kind indicator useful for appending to names."""
228
raise errors.BzrError('unknown kind %r' % self.kind)
230
raise BzrError('unknown kind %r' % self.kind)
230
232
known_kinds = ('file', 'directory', 'symlink')
234
def _put_in_tar(self, item, tree):
235
"""populate item for stashing in a tar, and return the content stream.
237
If no content is available, return None.
239
raise BzrError("don't know how to export {%s} of kind %r" %
240
(self.file_id, self.kind))
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.
246
This is a template method - implement _put_on_disk in subclasses.
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)
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))
232
257
def sorted_children(self):
233
258
return sorted(self.children.items())
252
277
if self.parent_id is not None:
253
278
if not inv.has_id(self.parent_id):
254
raise errors.BzrCheckError(
255
'missing parent {%s} in inventory for revision {%s}' % (
256
self.parent_id, rev_id))
279
raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
280
% (self.parent_id, rev_id))
257
281
checker._add_entry_to_text_key_references(inv, self)
258
282
self._check(checker, rev_id)
400
class RootEntry(InventoryEntry):
402
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
403
'text_id', 'parent_id', 'children', 'executable',
404
'revision', 'symlink_target', 'reference_revision']
406
def _check(self, checker, rev_id):
407
"""See InventoryEntry._check"""
409
def __init__(self, file_id):
410
self.file_id = file_id
412
self.kind = 'directory'
413
self.parent_id = None
416
symbol_versioning.warn('RootEntry is deprecated as of bzr 0.10.'
417
' Please use InventoryDirectory instead.',
418
DeprecationWarning, stacklevel=2)
420
def __eq__(self, other):
421
if not isinstance(other, RootEntry):
422
return NotImplemented
424
return (self.file_id == other.file_id) \
425
and (self.children == other.children)
376
428
class InventoryDirectory(InventoryEntry):
377
429
"""A directory in an inventory."""
379
__slots__ = ['children']
431
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
432
'text_id', 'parent_id', 'children', 'executable',
433
'revision', 'symlink_target', 'reference_revision']
383
435
def _check(self, checker, rev_id):
384
436
"""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))
385
441
# In non rich root repositories we do not expect a file graph for the
387
443
if self.name == '' and not checker.rich_roots:
403
459
def __init__(self, file_id, name, parent_id):
404
460
super(InventoryDirectory, self).__init__(file_id, name, parent_id)
405
461
self.children = {}
462
self.kind = 'directory'
407
464
def kind_character(self):
408
465
"""See InventoryEntry.kind_character."""
468
def _put_in_tar(self, item, tree):
469
"""See InventoryEntry._put_in_tar."""
470
item.type = tarfile.DIRTYPE
477
def _put_on_disk(self, fullpath, tree):
478
"""See InventoryEntry._put_on_disk."""
412
482
class InventoryFile(InventoryEntry):
413
483
"""A file in an inventory."""
415
__slots__ = ['text_sha1', 'text_size', 'text_id', 'executable']
419
def __init__(self, file_id, name, parent_id):
420
super(InventoryFile, self).__init__(file_id, name, parent_id)
421
self.text_sha1 = None
422
self.text_size = None
424
self.executable = False
485
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
486
'text_id', 'parent_id', 'children', 'executable',
487
'revision', 'symlink_target', 'reference_revision']
426
489
def _check(self, checker, tree_revision_id):
427
490
"""See InventoryEntry._check"""
470
533
"""See InventoryEntry.has_text."""
536
def __init__(self, file_id, name, parent_id):
537
super(InventoryFile, self).__init__(file_id, name, parent_id)
473
540
def kind_character(self):
474
541
"""See InventoryEntry.kind_character."""
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):
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)
477
561
def _read_tree_state(self, path, work_tree):
478
562
"""See InventoryEntry._read_tree_state."""
479
563
self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
511
595
class InventoryLink(InventoryEntry):
512
596
"""A file in an inventory."""
514
__slots__ = ['symlink_target']
518
def __init__(self, file_id, name, parent_id):
519
super(InventoryLink, self).__init__(file_id, name, parent_id)
520
self.symlink_target = None
598
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
599
'text_id', 'parent_id', 'children', 'executable',
600
'revision', 'symlink_target', 'reference_revision']
522
602
def _check(self, checker, tree_revision_id):
523
603
"""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))
524
608
if self.symlink_target is None:
525
609
checker._report_items.append(
526
610
'symlink {%s} has no target in revision {%s}'
564
648
differ = DiffSymlink(old_tree, new_tree, output_to)
565
649
return differ.diff_symlink(old_target, new_target)
651
def __init__(self, file_id, name, parent_id):
652
super(InventoryLink, self).__init__(file_id, name, parent_id)
653
self.kind = 'symlink'
567
655
def kind_character(self):
568
656
"""See InventoryEntry.kind_character."""
659
def _put_in_tar(self, item, tree):
660
"""See InventoryEntry._put_in_tar."""
661
item.type = tarfile.SYMTYPE
665
item.linkname = self.symlink_target
668
def _put_on_disk(self, fullpath, tree):
669
"""See InventoryEntry._put_on_disk."""
671
os.symlink(self.symlink_target, fullpath)
673
raise BzrError("Failed to create symlink %r -> %r, error: %s" % (fullpath, self.symlink_target, e))
571
675
def _read_tree_state(self, path, work_tree):
572
676
"""See InventoryEntry._read_tree_state."""
573
677
self.symlink_target = work_tree.get_symlink_target(self.file_id)
631
733
inserted, other than through the Inventory API.
634
@deprecated_method(deprecated_in((2, 4, 0)))
635
736
def __contains__(self, file_id):
636
737
"""True if this entry contains a file with given id.
638
739
>>> inv = Inventory()
639
740
>>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
640
741
InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
641
>>> inv.has_id('123')
643
>>> inv.has_id('456')
646
747
Note that this method along with __iter__ are not encouraged for use as
721
822
# if we finished all children, pop it off the stack
724
def _preload_cache(self):
725
"""Populate any caches, we are about to access all items.
727
The default implementation does nothing, because CommonInventory doesn't
732
825
def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
733
826
yield_parents=False):
734
827
"""Iterate over the entries in a directory first order.
747
840
specific_file_ids = set(specific_file_ids)
748
841
# TODO? Perhaps this should return the from_dir so that the root is
749
842
# yielded? or maybe an option?
750
if from_dir is None and specific_file_ids is None:
751
# They are iterating from the root, and have not specified any
752
# specific entries to look at. All current callers fully consume the
753
# iterator, so we can safely assume we are accessing all entries
754
self._preload_cache()
755
843
if from_dir is None:
756
844
if self.root is None:
825
913
file_id, self[file_id]))
916
def _get_mutable_inventory(self):
917
"""Returns a mutable copy of the object.
919
Some inventories are immutable, yet working trees, for example, needs
920
to mutate exisiting inventories instead of creating a new one.
922
raise NotImplementedError(self._get_mutable_inventory)
828
924
def make_entry(self, kind, name, parent_id, file_id=None):
829
925
"""Simple thunk to bzrlib.inventory.make_entry."""
830
926
return make_entry(kind, name, parent_id, file_id)
1173
1272
def _add_child(self, entry):
1174
1273
"""Add an entry to the inventory, without adding it to its parent"""
1175
1274
if entry.file_id in self._byid:
1176
raise errors.BzrError(
1177
"inventory already contains entry with id {%s}" %
1275
raise BzrError("inventory already contains entry with id {%s}" %
1179
1277
self._byid[entry.file_id] = entry
1180
1278
for child in getattr(entry, 'children', {}).itervalues():
1181
1279
self._add_child(child)
1346
1447
new_name = ensure_normalized_name(new_name)
1347
1448
if not is_valid_name(new_name):
1348
raise errors.BzrError("not an acceptable filename: %r" % new_name)
1449
raise BzrError("not an acceptable filename: %r" % new_name)
1350
1451
new_parent = self._byid[new_parent_id]
1351
1452
if new_name in new_parent.children:
1352
raise errors.BzrError("%r already exists in %r" %
1353
(new_name, self.id2path(new_parent_id)))
1453
raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
1355
1455
new_parent_idpath = self.get_idpath(new_parent_id)
1356
1456
if file_id in new_parent_idpath:
1357
raise errors.BzrError(
1358
"cannot move directory %r into a subdirectory of itself, %r"
1457
raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
1359
1458
% (self.id2path(file_id), self.id2path(new_parent_id)))
1361
1460
file_ie = self._byid[file_id]
1610
1708
self._fileid_to_entry_cache[result.file_id] = result
1711
def _get_mutable_inventory(self):
1712
"""See CommonInventory._get_mutable_inventory."""
1713
entries = self.iter_entries()
1714
inv = Inventory(None, self.revision_id)
1715
for path, inv_entry in entries:
1716
inv.add(inv_entry.copy())
1613
1719
def create_by_apply_delta(self, inventory_delta, new_revision_id,
1614
1720
propagate_caches=False):
1615
1721
"""Create a new CHKInventory by applying inventory_delta to this one.
1970
2076
self._fileid_to_entry_cache[file_id] = ie
1973
def _preload_cache(self):
1974
"""Make sure all file-ids are in _fileid_to_entry_cache"""
1975
if self._fully_cached:
1976
return # No need to do it again
1977
# The optimal sort order is to use iteritems() directly
1978
cache = self._fileid_to_entry_cache
1979
for key, entry in self.id_to_entry.iteritems():
1981
if file_id not in cache:
1982
ie = self._bytes_to_entry(entry)
1986
last_parent_id = last_parent_ie = None
1987
pid_items = self.parent_id_basename_to_file_id.iteritems()
1988
for key, child_file_id in pid_items:
1989
if key == ('', ''): # This is the root
1990
if child_file_id != self.root_id:
1991
raise ValueError('Data inconsistency detected.'
1992
' We expected data with key ("","") to match'
1993
' the root id, but %s != %s'
1994
% (child_file_id, self.root_id))
1996
parent_id, basename = key
1997
ie = cache[child_file_id]
1998
if parent_id == last_parent_id:
1999
parent_ie = last_parent_ie
2001
parent_ie = cache[parent_id]
2002
if parent_ie.kind != 'directory':
2003
raise ValueError('Data inconsistency detected.'
2004
' An entry in the parent_id_basename_to_file_id map'
2005
' has parent_id {%s} but the kind of that object'
2006
' is %r not "directory"' % (parent_id, parent_ie.kind))
2007
if parent_ie._children is None:
2008
parent_ie._children = {}
2009
basename = basename.decode('utf-8')
2010
if basename in parent_ie._children:
2011
existing_ie = parent_ie._children[basename]
2012
if existing_ie != ie:
2013
raise ValueError('Data inconsistency detected.'
2014
' Two entries with basename %r were found'
2015
' in the parent entry {%s}'
2016
% (basename, parent_id))
2017
if basename != ie.name:
2018
raise ValueError('Data inconsistency detected.'
2019
' In the parent_id_basename_to_file_id map, file_id'
2020
' {%s} is listed as having basename %r, but in the'
2021
' id_to_entry map it is %r'
2022
% (child_file_id, basename, ie.name))
2023
parent_ie._children[basename] = ie
2024
self._fully_cached = True
2026
2079
def iter_changes(self, basis):
2027
2080
"""Generate a Tree.iter_changes change list between this and basis.
2188
2241
class CHKInventoryDirectory(InventoryDirectory):
2189
2242
"""A directory in an inventory."""
2191
__slots__ = ['_children', '_chk_inventory']
2244
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
2245
'text_id', 'parent_id', '_children', 'executable',
2246
'revision', 'symlink_target', 'reference_revision',
2193
2249
def __init__(self, file_id, name, parent_id, chk_inventory):
2194
2250
# Don't call InventoryDirectory.__init__ - it isn't right for this
2196
2252
InventoryEntry.__init__(self, file_id, name, parent_id)
2197
2253
self._children = None
2254
self.kind = 'directory'
2198
2255
self._chk_inventory = chk_inventory
2383
2444
raise errors.InconsistentDelta(new_path, item[1],
2384
2445
"new_path with no entry")
2388
def mutable_inventory_from_tree(tree):
2389
"""Create a new inventory that has the same contents as a specified tree.
2391
:param tree: Revision tree to create inventory from
2393
entries = tree.iter_entries_by_dir()
2394
inv = Inventory(None, tree.get_revision_id())
2395
for path, inv_entry in entries:
2396
inv.add(inv_entry.copy())