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.
30
ROOT_ID = b"TREE_ROOT"
30
from bzrlib.lazy_import import lazy_import
32
from ..lazy_import import lazy_import
31
33
lazy_import(globals(), """
44
from breezy.bzr import (
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
53
from ..sixish import (
60
from ..static_tuple import StaticTuple
57
63
class InventoryEntry(object):
131
135
RENAMED = 'renamed'
132
136
MODIFIED_AND_RENAMED = 'modified and renamed'
138
__slots__ = ['file_id', 'revision', 'parent_id', 'name']
140
# Attributes that all InventoryEntry instances are expected to have, but
141
# that don't vary for all kinds of entry. (e.g. symlink_target is only
142
# relevant to InventoryLink, so there's no reason to make every
143
# InventoryFile instance allocate space to hold a value for it.)
144
# Attributes that only vary for files: executable, text_sha1, text_size,
150
# Attributes that only vary for symlinks: symlink_target
151
symlink_target = None
152
# Attributes that only vary for tree-references: reference_revision
153
reference_revision = None
136
156
def detect_changes(self, old_entry):
137
157
"""Return a (text_modified, meta_modified) from this to old_entry.
176
196
candidates[ie.revision] = ie
177
197
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
199
def has_text(self):
190
200
"""Return true if the object this entry represents has textual data.
212
222
Traceback (most recent call last):
213
223
InvalidEntryName: Invalid entry name: src/hello.c
215
if '/' in name or '\\' in name:
225
if u'/' in name or u'\\' in name:
216
226
raise errors.InvalidEntryName(name=name)
217
self.executable = False
227
self.file_id = file_id
218
228
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
230
self.parent_id = parent_id
225
self.symlink_target = None
226
self.reference_revision = None
228
232
def kind_character(self):
229
233
"""Return a short kind indicator useful for appending to names."""
230
raise BzrError('unknown kind %r' % self.kind)
234
raise errors.BzrError('unknown kind %r' % self.kind)
232
236
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
239
def versionable_kind(kind):
262
240
return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
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
379
class InventoryDirectory(InventoryEntry):
429
380
"""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']
382
__slots__ = ['children']
435
386
def _check(self, checker, rev_id):
436
387
"""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
388
# In non rich root repositories we do not expect a file graph for the
443
390
if self.name == '' and not checker.rich_roots:
446
393
# to provide a per-fileid log. The hash of every directory content is
447
394
# "da..." below (the sha1sum of '').
448
395
checker.add_pending_item(rev_id,
449
('texts', self.file_id, self.revision), 'text',
450
'da39a3ee5e6b4b0d3255bfef95601890afd80709')
396
(b'texts', self.file_id, self.revision), b'text',
397
b'da39a3ee5e6b4b0d3255bfef95601890afd80709')
453
400
other = InventoryDirectory(self.file_id, self.name, self.parent_id)
459
406
def __init__(self, file_id, name, parent_id):
460
407
super(InventoryDirectory, self).__init__(file_id, name, parent_id)
461
408
self.children = {}
462
self.kind = 'directory'
410
def sorted_children(self):
411
return sorted(viewitems(self.children))
464
413
def kind_character(self):
465
414
"""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
418
class InventoryFile(InventoryEntry):
483
419
"""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']
421
__slots__ = ['text_sha1', 'text_size', 'text_id', 'executable']
425
def __init__(self, file_id, name, parent_id):
426
super(InventoryFile, self).__init__(file_id, name, parent_id)
427
self.text_sha1 = None
428
self.text_size = None
430
self.executable = False
489
432
def _check(self, checker, tree_revision_id):
490
433
"""See InventoryEntry._check"""
491
434
# TODO: check size too.
492
435
checker.add_pending_item(tree_revision_id,
493
('texts', self.file_id, self.revision), 'text',
436
(b'texts', self.file_id, self.revision), b'text',
495
438
if self.text_size is None:
496
439
checker._report_items.append(
515
458
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
516
459
output_to, reverse=False):
517
460
"""See InventoryEntry._diff."""
518
from bzrlib.diff import DiffText
461
from breezy.diff import DiffText
519
462
from_file_id = self.file_id
521
464
to_file_id = to_entry.file_id
533
476
"""See InventoryEntry.has_text."""
536
def __init__(self, file_id, name, parent_id):
537
super(InventoryFile, self).__init__(file_id, name, parent_id)
540
479
def kind_character(self):
541
480
"""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
483
def _read_tree_state(self, path, work_tree):
562
484
"""See InventoryEntry._read_tree_state."""
563
485
self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
595
517
class InventoryLink(InventoryEntry):
596
518
"""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']
520
__slots__ = ['symlink_target']
524
def __init__(self, file_id, name, parent_id):
525
super(InventoryLink, self).__init__(file_id, name, parent_id)
526
self.symlink_target = None
602
528
def _check(self, checker, tree_revision_id):
603
529
"""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
530
if self.symlink_target is None:
609
531
checker._report_items.append(
610
532
'symlink {%s} has no target in revision {%s}'
611
533
% (self.file_id, tree_revision_id))
612
534
# Symlinks are stored as ''
613
535
checker.add_pending_item(tree_revision_id,
614
('texts', self.file_id, self.revision), 'text',
615
'da39a3ee5e6b4b0d3255bfef95601890afd80709')
536
(b'texts', self.file_id, self.revision), b'text',
537
b'da39a3ee5e6b4b0d3255bfef95601890afd80709')
618
540
other = InventoryLink(self.file_id, self.name, self.parent_id)
625
547
# FIXME: which _modified field should we use ? RBC 20051003
626
548
text_modified = (self.symlink_target != old_entry.symlink_target)
627
549
if text_modified:
628
mutter(" symlink target changed")
550
trace.mutter(" symlink target changed")
629
551
meta_modified = False
630
552
return text_modified, meta_modified
632
554
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
633
555
output_to, reverse=False):
634
556
"""See InventoryEntry._diff."""
635
from bzrlib.diff import DiffSymlink
557
from breezy.diff import DiffSymlink
636
558
old_target = self.symlink_target
637
559
if to_entry is not None:
638
560
new_target = to_entry.symlink_target
648
570
differ = DiffSymlink(old_tree, new_tree, output_to)
649
571
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
573
def kind_character(self):
656
574
"""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
577
def _read_tree_state(self, path, work_tree):
676
578
"""See InventoryEntry._read_tree_state."""
677
579
self.symlink_target = work_tree.get_symlink_target(self.file_id)
733
637
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
640
def has_filename(self, filename):
754
641
return bool(self.path2id(filename))
782
669
from_dir = self.root
783
670
yield '', self.root
784
elif isinstance(from_dir, basestring):
671
elif isinstance(from_dir, (str, text_type)):
785
672
from_dir = self[from_dir]
787
674
# unrolling the recursive called changed the time from
788
675
# 440ms/663ms (inline/total) to 116ms/116ms
789
children = from_dir.children.items()
676
children = sorted(viewitems(from_dir.children))
791
677
if not recursive:
792
678
for name, ie in children:
847
745
if (not yield_parents and specific_file_ids is not None and
848
746
len(specific_file_ids) == 1):
849
747
file_id = list(specific_file_ids)[0]
748
if self.has_id(file_id):
851
749
yield self.id2path(file_id), self[file_id]
853
751
from_dir = self.root
854
752
if (specific_file_ids is None or yield_parents or
855
753
self.root.file_id in specific_file_ids):
856
754
yield u'', self.root
857
elif isinstance(from_dir, basestring):
755
elif isinstance(from_dir, (str, text_type)):
858
756
from_dir = self[from_dir]
860
758
if specific_file_ids is not None:
913
811
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
814
def make_entry(self, kind, name, parent_id, file_id=None):
925
"""Simple thunk to bzrlib.inventory.make_entry."""
815
"""Simple thunk to breezy.bzr.inventory.make_entry."""
926
816
return make_entry(kind, name, parent_id, file_id)
928
818
def entries(self):
934
824
def descend(dir_ie, dir_path):
935
kids = dir_ie.children.items()
825
kids = sorted(viewitems(dir_ie.children))
937
826
for name, ie in kids:
938
827
child_path = osutils.pathjoin(dir_path, name)
939
828
accum.append((child_path, ie))
940
829
if ie.kind == 'directory':
941
830
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'')
832
if self.root is not None:
833
descend(self.root, u'')
962
836
def path2id(self, relpath):
1272
1141
def _add_child(self, entry):
1273
1142
"""Add an entry to the inventory, without adding it to its parent"""
1274
1143
if entry.file_id in self._byid:
1275
raise BzrError("inventory already contains entry with id {%s}" %
1144
raise errors.BzrError(
1145
"inventory already contains entry with id {%s}" %
1277
1147
self._byid[entry.file_id] = entry
1278
for child in getattr(entry, 'children', {}).itervalues():
1279
self._add_child(child)
1148
children = getattr(entry, 'children', {})
1149
if children is not None:
1150
for child in viewvalues(children):
1151
self._add_child(child)
1282
1154
def add(self, entry):
1283
1155
"""Add entry to inventory.
1285
To add a file to a branch ready to be committed, use Branch.add,
1290
1159
if entry.file_id in self._byid:
1447
1316
new_name = ensure_normalized_name(new_name)
1448
1317
if not is_valid_name(new_name):
1449
raise BzrError("not an acceptable filename: %r" % new_name)
1318
raise errors.BzrError("not an acceptable filename: %r" % new_name)
1451
1320
new_parent = self._byid[new_parent_id]
1452
1321
if new_name in new_parent.children:
1453
raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
1322
raise errors.BzrError("%r already exists in %r" %
1323
(new_name, self.id2path(new_parent_id)))
1455
1325
new_parent_idpath = self.get_idpath(new_parent_id)
1456
1326
if file_id in new_parent_idpath:
1457
raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
1327
raise errors.BzrError(
1328
"cannot move directory %r into a subdirectory of itself, %r"
1458
1329
% (self.id2path(file_id), self.id2path(new_parent_id)))
1460
1331
file_ie = self._byid[file_id]
1531
1403
if entry.parent_id is not None:
1532
1404
parent_str = entry.parent_id
1535
1407
name_str = entry.name.encode("utf8")
1536
1408
if entry.kind == 'file':
1537
1409
if entry.executable:
1541
return "file: %s\n%s\n%s\n%s\n%s\n%d\n%s" % (
1413
return b"file: %s\n%s\n%s\n%s\n%s\n%d\n%s" % (
1542
1414
entry.file_id, parent_str, name_str, entry.revision,
1543
1415
entry.text_sha1, entry.text_size, exec_str)
1544
1416
elif entry.kind == 'directory':
1545
return "dir: %s\n%s\n%s\n%s" % (
1417
return b"dir: %s\n%s\n%s\n%s" % (
1546
1418
entry.file_id, parent_str, name_str, entry.revision)
1547
1419
elif entry.kind == 'symlink':
1548
return "symlink: %s\n%s\n%s\n%s\n%s" % (
1420
return b"symlink: %s\n%s\n%s\n%s\n%s" % (
1549
1421
entry.file_id, parent_str, name_str, entry.revision,
1550
1422
entry.symlink_target.encode("utf8"))
1551
1423
elif entry.kind == 'tree-reference':
1552
return "tree: %s\n%s\n%s\n%s\n%s" % (
1424
return b"tree: %s\n%s\n%s\n%s\n%s" % (
1553
1425
entry.file_id, parent_str, name_str, entry.revision,
1554
1426
entry.reference_revision)
1617
1489
keys = [StaticTuple(f,).intern() for f in directories_to_expand]
1618
1490
directories_to_expand = set()
1619
1491
items = self.parent_id_basename_to_file_id.iteritems(keys)
1620
next_file_ids = set([item[1] for item in items])
1492
next_file_ids = {item[1] for item in items}
1621
1493
next_file_ids = next_file_ids.difference(interesting)
1622
1494
interesting.update(next_file_ids)
1623
1495
for entry in self._getitems(next_file_ids):
1624
1496
if entry.kind == 'directory':
1625
1497
directories_to_expand.add(entry.file_id)
1626
children_of_parent_id.setdefault(entry.parent_id, []
1627
).append(entry.file_id)
1498
children_of_parent_id.setdefault(entry.parent_id, set()
1499
).add(entry.file_id)
1628
1500
return interesting, children_of_parent_id
1630
1502
def filter(self, specific_fileids):
1674
def _bytes_to_utf8name_key(bytes):
1675
"""Get the file_id, revision_id key out of bytes."""
1542
def _bytes_to_utf8name_key(data):
1543
"""Get the file_id, revision_id key out of data."""
1676
1544
# We don't normally care about name, except for times when we want
1677
1545
# to filter out empty names because of non rich-root...
1678
sections = bytes.split('\n')
1679
kind, file_id = sections[0].split(': ')
1680
return (sections[2], intern(file_id), intern(sections[3]))
1546
sections = data.split(b'\n')
1547
kind, file_id = sections[0].split(b': ')
1548
return (sections[2], bytesintern(file_id), bytesintern(sections[3]))
1682
1550
def _bytes_to_entry(self, bytes):
1683
1551
"""Deserialise a serialised entry."""
1684
sections = bytes.split('\n')
1685
if sections[0].startswith("file: "):
1552
sections = bytes.split(b'\n')
1553
if sections[0].startswith(b"file: "):
1686
1554
result = InventoryFile(sections[0][6:],
1687
1555
sections[2].decode('utf8'),
1689
1557
result.text_sha1 = sections[4]
1690
1558
result.text_size = int(sections[5])
1691
result.executable = sections[6] == "Y"
1692
elif sections[0].startswith("dir: "):
1559
result.executable = sections[6] == b"Y"
1560
elif sections[0].startswith(b"dir: "):
1693
1561
result = CHKInventoryDirectory(sections[0][5:],
1694
1562
sections[2].decode('utf8'),
1695
1563
sections[1], self)
1696
elif sections[0].startswith("symlink: "):
1564
elif sections[0].startswith(b"symlink: "):
1697
1565
result = InventoryLink(sections[0][9:],
1698
1566
sections[2].decode('utf8'),
1700
1568
result.symlink_target = sections[4].decode('utf8')
1701
elif sections[0].startswith("tree: "):
1569
elif sections[0].startswith(b"tree: "):
1702
1570
result = TreeReference(sections[0][6:],
1703
1571
sections[2].decode('utf8'),
1705
1573
result.reference_revision = sections[4]
1707
1575
raise ValueError("Not a serialised entry %r" % bytes)
1708
result.file_id = intern(result.file_id)
1709
result.revision = intern(sections[3])
1710
if result.parent_id == '':
1576
result.file_id = bytesintern(result.file_id)
1577
result.revision = bytesintern(sections[3])
1578
if result.parent_id == b'':
1711
1579
result.parent_id = None
1712
1580
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
1583
def create_by_apply_delta(self, inventory_delta, new_revision_id,
1724
1584
propagate_caches=False):
1725
1585
"""Create a new CHKInventory by applying inventory_delta to this one.
1900
1760
:return: A CHKInventory
1902
lines = bytes.split('\n')
1762
lines = bytes.split(b'\n')
1763
if lines[-1] != b'':
1904
1764
raise AssertionError('bytes to deserialize must end with an eol')
1906
if lines[0] != 'chkinventory:':
1766
if lines[0] != b'chkinventory:':
1907
1767
raise ValueError("not a serialised CHKInventory: %r" % bytes)
1909
allowed_keys = frozenset(['root_id', 'revision_id', 'search_key_name',
1910
'parent_id_basename_to_file_id',
1769
allowed_keys = frozenset((b'root_id', b'revision_id',
1770
b'parent_id_basename_to_file_id',
1771
b'search_key_name', b'id_to_entry'))
1912
1772
for line in lines[1:]:
1913
key, value = line.split(': ', 1)
1773
key, value = line.split(b': ', 1)
1914
1774
if key not in allowed_keys:
1915
1775
raise errors.BzrError('Unknown key in inventory: %r\n%r'
1916
1776
% (key, bytes))
1918
1778
raise errors.BzrError('Duplicate key in inventory: %r\n%r'
1919
1779
% (key, bytes))
1920
1780
info[key] = value
1921
revision_id = intern(info['revision_id'])
1922
root_id = intern(info['root_id'])
1923
search_key_name = intern(info.get('search_key_name', 'plain'))
1924
parent_id_basename_to_file_id = intern(info.get(
1925
'parent_id_basename_to_file_id', None))
1926
if not parent_id_basename_to_file_id.startswith('sha1:'):
1781
revision_id = bytesintern(info[b'revision_id'])
1782
root_id = bytesintern(info[b'root_id'])
1783
search_key_name = bytesintern(info.get(b'search_key_name', b'plain'))
1784
parent_id_basename_to_file_id = bytesintern(info.get(
1785
b'parent_id_basename_to_file_id', None))
1786
if not parent_id_basename_to_file_id.startswith(b'sha1:'):
1927
1787
raise ValueError('parent_id_basename_to_file_id should be a sha1'
1928
1788
' key not %r' % (parent_id_basename_to_file_id,))
1929
id_to_entry = info['id_to_entry']
1930
if not id_to_entry.startswith('sha1:'):
1789
id_to_entry = info[b'id_to_entry']
1790
if not id_to_entry.startswith(b'sha1:'):
1931
1791
raise ValueError('id_to_entry should be a sha1'
1932
1792
' key not %r' % (id_to_entry,))
2080
1940
self._fileid_to_entry_cache[file_id] = ie
1943
def _preload_cache(self):
1944
"""Make sure all file-ids are in _fileid_to_entry_cache"""
1945
if self._fully_cached:
1946
return # No need to do it again
1947
# The optimal sort order is to use iteritems() directly
1948
cache = self._fileid_to_entry_cache
1949
for key, entry in self.id_to_entry.iteritems():
1951
if file_id not in cache:
1952
ie = self._bytes_to_entry(entry)
1956
last_parent_id = last_parent_ie = None
1957
pid_items = self.parent_id_basename_to_file_id.iteritems()
1958
for key, child_file_id in pid_items:
1959
if key == (b'', b''): # This is the root
1960
if child_file_id != self.root_id:
1961
raise ValueError('Data inconsistency detected.'
1962
' We expected data with key ("","") to match'
1963
' the root id, but %s != %s'
1964
% (child_file_id, self.root_id))
1966
parent_id, basename = key
1967
ie = cache[child_file_id]
1968
if parent_id == last_parent_id:
1969
parent_ie = last_parent_ie
1971
parent_ie = cache[parent_id]
1972
if parent_ie.kind != 'directory':
1973
raise ValueError('Data inconsistency detected.'
1974
' An entry in the parent_id_basename_to_file_id map'
1975
' has parent_id {%s} but the kind of that object'
1976
' is %r not "directory"' % (parent_id, parent_ie.kind))
1977
if parent_ie._children is None:
1978
parent_ie._children = {}
1979
basename = basename.decode('utf-8')
1980
if basename in parent_ie._children:
1981
existing_ie = parent_ie._children[basename]
1982
if existing_ie != ie:
1983
raise ValueError('Data inconsistency detected.'
1984
' Two entries with basename %r were found'
1985
' in the parent entry {%s}'
1986
% (basename, parent_id))
1987
if basename != ie.name:
1988
raise ValueError('Data inconsistency detected.'
1989
' In the parent_id_basename_to_file_id map, file_id'
1990
' {%s} is listed as having basename %r, but in the'
1991
' id_to_entry map it is %r'
1992
% (child_file_id, basename, ie.name))
1993
parent_ie._children[basename] = ie
1994
self._fully_cached = True
2083
1996
def iter_changes(self, basis):
2084
1997
"""Generate a Tree.iter_changes change list between this and basis.
2182
2095
def path2id(self, relpath):
2183
2096
"""See CommonInventory.path2id()."""
2184
2097
# TODO: perhaps support negative hits?
2098
if isinstance(relpath, (str, text_type)):
2099
names = osutils.splitpath(relpath)
2104
relpath = osutils.pathjoin(*relpath)
2185
2105
result = self._path_to_fileid_cache.get(relpath, None)
2186
2106
if result is not None:
2188
if isinstance(relpath, basestring):
2189
names = osutils.splitpath(relpath)
2192
2108
current_id = self.root_id
2193
2109
if current_id is None:
2219
2135
def to_lines(self):
2220
2136
"""Serialise the inventory to lines."""
2221
lines = ["chkinventory:\n"]
2137
lines = [b"chkinventory:\n"]
2222
2138
if self._search_key_name != 'plain':
2223
2139
# custom ordering grouping things that don't change together
2224
lines.append('search_key_name: %s\n' % (self._search_key_name,))
2225
lines.append("root_id: %s\n" % self.root_id)
2226
lines.append('parent_id_basename_to_file_id: %s\n' %
2140
lines.append(b'search_key_name: %s\n' % (
2141
self._search_key_name.encode('ascii')))
2142
lines.append(b"root_id: %s\n" % self.root_id)
2143
lines.append(b'parent_id_basename_to_file_id: %s\n' %
2227
2144
(self.parent_id_basename_to_file_id.key()[0],))
2228
lines.append("revision_id: %s\n" % self.revision_id)
2229
lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2145
lines.append(b"revision_id: %s\n" % self.revision_id)
2146
lines.append(b"id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2231
lines.append("revision_id: %s\n" % self.revision_id)
2232
lines.append("root_id: %s\n" % self.root_id)
2148
lines.append(b"revision_id: %s\n" % self.revision_id)
2149
lines.append(b"root_id: %s\n" % self.root_id)
2233
2150
if self.parent_id_basename_to_file_id is not None:
2234
lines.append('parent_id_basename_to_file_id: %s\n' %
2151
lines.append(b'parent_id_basename_to_file_id: %s\n' %
2235
2152
(self.parent_id_basename_to_file_id.key()[0],))
2236
lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2153
lines.append(b"id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2245
2162
class CHKInventoryDirectory(InventoryDirectory):
2246
2163
"""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',
2165
__slots__ = ['_children', '_chk_inventory']
2253
2167
def __init__(self, file_id, name, parent_id, chk_inventory):
2254
2168
# Don't call InventoryDirectory.__init__ - it isn't right for this
2256
2170
InventoryEntry.__init__(self, file_id, name, parent_id)
2257
2171
self._children = None
2258
self.kind = 'directory'
2259
2172
self._chk_inventory = chk_inventory
2448
2357
raise errors.InconsistentDelta(new_path, item[1],
2449
2358
"new_path with no entry")
2362
def mutable_inventory_from_tree(tree):
2363
"""Create a new inventory that has the same contents as a specified tree.
2365
:param tree: Revision tree to create inventory from
2367
entries = tree.iter_entries_by_dir()
2368
inv = Inventory(None, tree.get_revision_id())
2369
for path, inv_entry in entries:
2370
inv.add(inv_entry.copy())