23
23
# But those depend on its position within a particular inventory, and
24
24
# it would be nice not to need to hold the backpointer here.
26
from __future__ import absolute_import
26
28
# This should really be an id randomly assigned when the tree is
27
29
# created, but it's not for now.
28
30
ROOT_ID = "TREE_ROOT"
30
from bzrlib.lazy_import import lazy_import
32
from .lazy_import import lazy_import
31
33
lazy_import(globals(), """
48
from bzrlib.errors import (
52
from bzrlib.symbol_versioning import deprecated_in, deprecated_method
53
from bzrlib.trace import mutter
54
from bzrlib.static_tuple import StaticTuple
55
from .static_tuple import StaticTuple
57
58
class InventoryEntry(object):
104
105
InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
105
106
>>> i.path2id('src/wibble')
109
108
>>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
110
109
InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None, revision=None)
131
130
RENAMED = 'renamed'
132
131
MODIFIED_AND_RENAMED = 'modified and renamed'
133
__slots__ = ['file_id', 'revision', 'parent_id', 'name']
135
# Attributes that all InventoryEntry instances are expected to have, but
136
# that don't vary for all kinds of entry. (e.g. symlink_target is only
137
# relevant to InventoryLink, so there's no reason to make every
138
# InventoryFile instance allocate space to hold a value for it.)
139
# Attributes that only vary for files: executable, text_sha1, text_size,
145
# Attributes that only vary for symlinks: symlink_target
146
symlink_target = None
147
# Attributes that only vary for tree-references: reference_revision
148
reference_revision = None
136
151
def detect_changes(self, old_entry):
137
152
"""Return a (text_modified, meta_modified) from this to old_entry.
176
191
candidates[ie.revision] = ie
177
192
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)
189
194
def has_text(self):
190
195
"""Return true if the object this entry represents has textual data.
200
def __init__(self, file_id, name, parent_id, text_id=None):
205
def __init__(self, file_id, name, parent_id):
201
206
"""Create an InventoryEntry
203
208
The filename must be a single component, relative to the
215
220
if '/' in name or '\\' in name:
216
221
raise errors.InvalidEntryName(name=name)
217
self.executable = False
222
self.file_id = file_id
218
223
self.revision = None
219
self.text_sha1 = None
220
self.text_size = None
221
self.file_id = file_id
223
self.text_id = text_id
224
225
self.parent_id = parent_id
225
self.symlink_target = None
226
self.reference_revision = None
228
227
def kind_character(self):
229
228
"""Return a short kind indicator useful for appending to names."""
230
raise BzrError('unknown kind %r' % self.kind)
229
raise errors.BzrError('unknown kind %r' % self.kind)
232
231
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))
257
def sorted_children(self):
258
return sorted(self.children.items())
261
234
def versionable_kind(kind):
262
235
return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
277
250
if self.parent_id is not None:
278
251
if not inv.has_id(self.parent_id):
279
raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
280
% (self.parent_id, rev_id))
252
raise errors.BzrCheckError(
253
'missing parent {%s} in inventory for revision {%s}' % (
254
self.parent_id, rev_id))
281
255
checker._add_entry_to_text_key_references(inv, self)
282
256
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)
428
374
class InventoryDirectory(InventoryEntry):
429
375
"""A directory in an inventory."""
431
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
432
'text_id', 'parent_id', 'children', 'executable',
433
'revision', 'symlink_target', 'reference_revision']
377
__slots__ = ['children']
435
381
def _check(self, checker, rev_id):
436
382
"""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))
441
383
# In non rich root repositories we do not expect a file graph for the
443
385
if self.name == '' and not checker.rich_roots:
459
401
def __init__(self, file_id, name, parent_id):
460
402
super(InventoryDirectory, self).__init__(file_id, name, parent_id)
461
403
self.children = {}
462
self.kind = 'directory'
405
def sorted_children(self):
406
return sorted(viewitems(self.children))
464
408
def kind_character(self):
465
409
"""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."""
482
413
class InventoryFile(InventoryEntry):
483
414
"""A file in an inventory."""
485
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
486
'text_id', 'parent_id', 'children', 'executable',
487
'revision', 'symlink_target', 'reference_revision']
416
__slots__ = ['text_sha1', 'text_size', 'text_id', 'executable']
420
def __init__(self, file_id, name, parent_id):
421
super(InventoryFile, self).__init__(file_id, name, parent_id)
422
self.text_sha1 = None
423
self.text_size = None
425
self.executable = False
489
427
def _check(self, checker, tree_revision_id):
490
428
"""See InventoryEntry._check"""
515
453
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
516
454
output_to, reverse=False):
517
455
"""See InventoryEntry._diff."""
518
from bzrlib.diff import DiffText
456
from breezy.diff import DiffText
519
457
from_file_id = self.file_id
521
459
to_file_id = to_entry.file_id
533
471
"""See InventoryEntry.has_text."""
536
def __init__(self, file_id, name, parent_id):
537
super(InventoryFile, self).__init__(file_id, name, parent_id)
540
474
def kind_character(self):
541
475
"""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)
561
478
def _read_tree_state(self, path, work_tree):
562
479
"""See InventoryEntry._read_tree_state."""
563
480
self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
595
512
class InventoryLink(InventoryEntry):
596
513
"""A file in an inventory."""
598
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
599
'text_id', 'parent_id', 'children', 'executable',
600
'revision', 'symlink_target', 'reference_revision']
515
__slots__ = ['symlink_target']
519
def __init__(self, file_id, name, parent_id):
520
super(InventoryLink, self).__init__(file_id, name, parent_id)
521
self.symlink_target = None
602
523
def _check(self, checker, tree_revision_id):
603
524
"""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))
608
525
if self.symlink_target is None:
609
526
checker._report_items.append(
610
527
'symlink {%s} has no target in revision {%s}'
625
542
# FIXME: which _modified field should we use ? RBC 20051003
626
543
text_modified = (self.symlink_target != old_entry.symlink_target)
627
544
if text_modified:
628
mutter(" symlink target changed")
545
trace.mutter(" symlink target changed")
629
546
meta_modified = False
630
547
return text_modified, meta_modified
632
549
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
633
550
output_to, reverse=False):
634
551
"""See InventoryEntry._diff."""
635
from bzrlib.diff import DiffSymlink
552
from breezy.diff import DiffSymlink
636
553
old_target = self.symlink_target
637
554
if to_entry is not None:
638
555
new_target = to_entry.symlink_target
648
565
differ = DiffSymlink(old_tree, new_tree, output_to)
649
566
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'
655
568
def kind_character(self):
656
569
"""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))
675
572
def _read_tree_state(self, path, work_tree):
676
573
"""See InventoryEntry._read_tree_state."""
677
574
self.symlink_target = work_tree.get_symlink_target(self.file_id)
733
632
inserted, other than through the Inventory API.
736
def __contains__(self, file_id):
737
"""True if this entry contains a file with given id.
739
>>> inv = Inventory()
740
>>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
741
InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
747
Note that this method along with __iter__ are not encouraged for use as
748
they are less clear than specific query methods - they may be rmeoved
751
return self.has_id(file_id)
753
635
def has_filename(self, filename):
754
636
return bool(self.path2id(filename))
814
695
# But do this child first
815
new_children = ie.children.items()
696
new_children = sorted(viewitems(ie.children))
817
697
new_children = collections.deque(new_children)
818
698
stack.append((path, new_children))
819
699
# Break out of inner loop, so that we start outer loop with child
822
702
# if we finished all children, pop it off the stack
705
def _preload_cache(self):
706
"""Populate any caches, we are about to access all items.
708
The default implementation does nothing, because CommonInventory doesn't
825
713
def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
826
714
yield_parents=False):
827
715
"""Iterate over the entries in a directory first order.
840
728
specific_file_ids = set(specific_file_ids)
841
729
# TODO? Perhaps this should return the from_dir so that the root is
842
730
# yielded? or maybe an option?
731
if from_dir is None and specific_file_ids is None:
732
# They are iterating from the root, and have not specified any
733
# specific entries to look at. All current callers fully consume the
734
# iterator, so we can safely assume we are accessing all entries
735
self._preload_cache()
843
736
if from_dir is None:
844
737
if self.root is None:
913
806
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)
924
809
def make_entry(self, kind, name, parent_id, file_id=None):
925
"""Simple thunk to bzrlib.inventory.make_entry."""
810
"""Simple thunk to breezy.inventory.make_entry."""
926
811
return make_entry(kind, name, parent_id, file_id)
928
813
def entries(self):
934
819
def descend(dir_ie, dir_path):
935
kids = dir_ie.children.items()
820
kids = sorted(viewitems(dir_ie.children))
937
821
for name, ie in kids:
938
822
child_path = osutils.pathjoin(dir_path, name)
939
823
accum.append((child_path, ie))
940
824
if ie.kind == 'directory':
941
825
descend(ie, child_path)
943
descend(self.root, u'')
946
def directories(self):
947
"""Return (path, entry) pairs for all directories, including the root.
950
def descend(parent_ie, parent_path):
951
accum.append((parent_path, parent_ie))
953
kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
956
for name, child_ie in kids:
957
child_path = osutils.pathjoin(parent_path, name)
958
descend(child_ie, child_path)
959
descend(self.root, u'')
827
if self.root is not None:
828
descend(self.root, u'')
962
831
def path2id(self, relpath):
1272
1136
def _add_child(self, entry):
1273
1137
"""Add an entry to the inventory, without adding it to its parent"""
1274
1138
if entry.file_id in self._byid:
1275
raise BzrError("inventory already contains entry with id {%s}" %
1139
raise errors.BzrError(
1140
"inventory already contains entry with id {%s}" %
1277
1142
self._byid[entry.file_id] = entry
1278
for child in getattr(entry, 'children', {}).itervalues():
1279
self._add_child(child)
1143
children = getattr(entry, 'children', {})
1144
if children is not None:
1145
for child in viewvalues(children):
1146
self._add_child(child)
1282
1149
def add(self, entry):
1283
1150
"""Add entry to inventory.
1285
To add a file to a branch ready to be committed, use Branch.add,
1290
1154
if entry.file_id in self._byid:
1447
1311
new_name = ensure_normalized_name(new_name)
1448
1312
if not is_valid_name(new_name):
1449
raise BzrError("not an acceptable filename: %r" % new_name)
1313
raise errors.BzrError("not an acceptable filename: %r" % new_name)
1451
1315
new_parent = self._byid[new_parent_id]
1452
1316
if new_name in new_parent.children:
1453
raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
1317
raise errors.BzrError("%r already exists in %r" %
1318
(new_name, self.id2path(new_parent_id)))
1455
1320
new_parent_idpath = self.get_idpath(new_parent_id)
1456
1321
if file_id in new_parent_idpath:
1457
raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
1322
raise errors.BzrError(
1323
"cannot move directory %r into a subdirectory of itself, %r"
1458
1324
% (self.id2path(file_id), self.id2path(new_parent_id)))
1460
1326
file_ie = self._byid[file_id]
1588
1455
if entry.kind == 'directory':
1589
1456
directories_to_expand.add(entry.file_id)
1590
1457
interesting.add(entry.parent_id)
1591
children_of_parent_id.setdefault(entry.parent_id, []
1592
).append(entry.file_id)
1458
children_of_parent_id.setdefault(entry.parent_id, set()
1459
).add(entry.file_id)
1594
1461
# Now, interesting has all of the direct parents, but not the
1595
1462
# parents of those parents. It also may have some duplicates with
1603
1470
next_parents = set()
1604
1471
for entry in self._getitems(remaining_parents):
1605
1472
next_parents.add(entry.parent_id)
1606
children_of_parent_id.setdefault(entry.parent_id, []
1607
).append(entry.file_id)
1473
children_of_parent_id.setdefault(entry.parent_id, set()
1474
).add(entry.file_id)
1608
1475
# Remove any search tips we've already processed
1609
1476
remaining_parents = next_parents.difference(interesting)
1610
1477
interesting.update(remaining_parents)
1617
1484
keys = [StaticTuple(f,).intern() for f in directories_to_expand]
1618
1485
directories_to_expand = set()
1619
1486
items = self.parent_id_basename_to_file_id.iteritems(keys)
1620
next_file_ids = set([item[1] for item in items])
1487
next_file_ids = {item[1] for item in items}
1621
1488
next_file_ids = next_file_ids.difference(interesting)
1622
1489
interesting.update(next_file_ids)
1623
1490
for entry in self._getitems(next_file_ids):
1624
1491
if entry.kind == 'directory':
1625
1492
directories_to_expand.add(entry.file_id)
1626
children_of_parent_id.setdefault(entry.parent_id, []
1627
).append(entry.file_id)
1493
children_of_parent_id.setdefault(entry.parent_id, set()
1494
).add(entry.file_id)
1628
1495
return interesting, children_of_parent_id
1630
1497
def filter(self, specific_fileids):
1652
1519
# parent_to_children with at least the tree root.)
1654
1521
cache = self._fileid_to_entry_cache
1656
remaining_children = collections.deque(parent_to_children[self.root_id])
1658
import pdb; pdb.set_trace()
1522
remaining_children = collections.deque(parent_to_children[self.root_id])
1660
1523
while remaining_children:
1661
1524
file_id = remaining_children.popleft()
1662
1525
ie = cache[file_id]
1712
1575
self._fileid_to_entry_cache[result.file_id] = result
1715
def _get_mutable_inventory(self):
1716
"""See CommonInventory._get_mutable_inventory."""
1717
entries = self.iter_entries()
1718
inv = Inventory(None, self.revision_id)
1719
for path, inv_entry in entries:
1720
inv.add(inv_entry.copy())
1723
1578
def create_by_apply_delta(self, inventory_delta, new_revision_id,
1724
1579
propagate_caches=False):
1725
1580
"""Create a new CHKInventory by applying inventory_delta to this one.
1738
1593
result = CHKInventory(self._search_key_name)
1739
1594
if propagate_caches:
1740
1595
# Just propagate the path-to-fileid cache for now
1741
result._path_to_fileid_cache = dict(self._path_to_fileid_cache.iteritems())
1596
result._path_to_fileid_cache = self._path_to_fileid_cache.copy()
1742
1597
search_key_func = chk_map.search_key_registry.get(self._search_key_name)
1743
1598
self.id_to_entry._ensure_root()
1744
1599
maximum_size = self.id_to_entry._root_node.maximum_size
1858
1713
# This loop could potentially be better by using the id_basename
1859
1714
# map to just get the child file ids.
1860
for child in entry.children.values():
1715
for child in viewvalues(entry.children):
1861
1716
if child.file_id not in altered:
1862
1717
raise errors.InconsistentDelta(self.id2path(child.file_id),
1863
1718
child.file_id, "Child not deleted or reparented when "
1869
1724
# re-keying, but its simpler to just output that as a delete+add
1870
1725
# to spend less time calculating the delta.
1871
1726
delta_list = []
1872
for key, (old_key, value) in parent_id_basename_delta.iteritems():
1727
for key, (old_key, value) in viewitems(parent_id_basename_delta):
1873
1728
if value is not None:
1874
1729
delta_list.append((old_key, key, value))
2080
1935
self._fileid_to_entry_cache[file_id] = ie
1938
def _preload_cache(self):
1939
"""Make sure all file-ids are in _fileid_to_entry_cache"""
1940
if self._fully_cached:
1941
return # No need to do it again
1942
# The optimal sort order is to use iteritems() directly
1943
cache = self._fileid_to_entry_cache
1944
for key, entry in self.id_to_entry.iteritems():
1946
if file_id not in cache:
1947
ie = self._bytes_to_entry(entry)
1951
last_parent_id = last_parent_ie = None
1952
pid_items = self.parent_id_basename_to_file_id.iteritems()
1953
for key, child_file_id in pid_items:
1954
if key == ('', ''): # This is the root
1955
if child_file_id != self.root_id:
1956
raise ValueError('Data inconsistency detected.'
1957
' We expected data with key ("","") to match'
1958
' the root id, but %s != %s'
1959
% (child_file_id, self.root_id))
1961
parent_id, basename = key
1962
ie = cache[child_file_id]
1963
if parent_id == last_parent_id:
1964
parent_ie = last_parent_ie
1966
parent_ie = cache[parent_id]
1967
if parent_ie.kind != 'directory':
1968
raise ValueError('Data inconsistency detected.'
1969
' An entry in the parent_id_basename_to_file_id map'
1970
' has parent_id {%s} but the kind of that object'
1971
' is %r not "directory"' % (parent_id, parent_ie.kind))
1972
if parent_ie._children is None:
1973
parent_ie._children = {}
1974
basename = basename.decode('utf-8')
1975
if basename in parent_ie._children:
1976
existing_ie = parent_ie._children[basename]
1977
if existing_ie != ie:
1978
raise ValueError('Data inconsistency detected.'
1979
' Two entries with basename %r were found'
1980
' in the parent entry {%s}'
1981
% (basename, parent_id))
1982
if basename != ie.name:
1983
raise ValueError('Data inconsistency detected.'
1984
' In the parent_id_basename_to_file_id map, file_id'
1985
' {%s} is listed as having basename %r, but in the'
1986
' id_to_entry map it is %r'
1987
% (child_file_id, basename, ie.name))
1988
parent_ie._children[basename] = ie
1989
self._fully_cached = True
2083
1991
def iter_changes(self, basis):
2084
1992
"""Generate a Tree.iter_changes change list between this and basis.
2160
2068
def _make_delta(self, old):
2161
2069
"""Make an inventory delta from two inventories."""
2162
if type(old) != CHKInventory:
2070
if not isinstance(old, CHKInventory):
2163
2071
return CommonInventory._make_delta(self, old)
2165
2073
for key, old_value, self_value in \
2182
2090
def path2id(self, relpath):
2183
2091
"""See CommonInventory.path2id()."""
2184
2092
# TODO: perhaps support negative hits?
2093
if isinstance(relpath, basestring):
2094
names = osutils.splitpath(relpath)
2099
relpath = osutils.pathjoin(*relpath)
2185
2100
result = self._path_to_fileid_cache.get(relpath, None)
2186
2101
if result is not None:
2188
if isinstance(relpath, basestring):
2189
names = osutils.splitpath(relpath)
2192
2103
current_id = self.root_id
2193
2104
if current_id is None:
2245
2156
class CHKInventoryDirectory(InventoryDirectory):
2246
2157
"""A directory in an inventory."""
2248
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
2249
'text_id', 'parent_id', '_children', 'executable',
2250
'revision', 'symlink_target', 'reference_revision',
2159
__slots__ = ['_children', '_chk_inventory']
2253
2161
def __init__(self, file_id, name, parent_id, chk_inventory):
2254
2162
# Don't call InventoryDirectory.__init__ - it isn't right for this
2256
2164
InventoryEntry.__init__(self, file_id, name, parent_id)
2257
2165
self._children = None
2258
self.kind = 'directory'
2259
2166
self._chk_inventory = chk_inventory
2331
2238
accessed on this platform by the normalized path.
2332
2239
:return: The NFC normalised version of name.
2334
#------- This has been copied to bzrlib.dirstate.DirState.add, please
2241
#------- This has been copied to breezy.dirstate.DirState.add, please
2335
2242
# keep them synchronised.
2336
2243
# we dont import normalized_filename directly because we want to be
2337
2244
# able to change the implementation at runtime for tests.
2413
2316
if item[2] is None:
2414
2317
raise errors.InconsistentDelta(item[0] or item[1], item[2],
2415
2318
"entry with file_id None %r" % entry)
2416
if type(item[2]) != str:
2319
if not isinstance(item[2], str):
2417
2320
raise errors.InconsistentDelta(item[0] or item[1], item[2],
2418
2321
"entry with non bytes file_id %r" % entry)
2448
2351
raise errors.InconsistentDelta(new_path, item[1],
2449
2352
"new_path with no entry")
2356
def mutable_inventory_from_tree(tree):
2357
"""Create a new inventory that has the same contents as a specified tree.
2359
:param tree: Revision tree to create inventory from
2361
entries = tree.iter_entries_by_dir()
2362
inv = Inventory(None, tree.get_revision_id())
2363
for path, inv_entry in entries:
2364
inv.add(inv_entry.copy())