130
128
RENAMED = 'renamed'
131
129
MODIFIED_AND_RENAMED = 'modified and renamed'
131
__slots__ = ['file_id', 'revision', 'parent_id', 'name']
133
# Attributes that all InventoryEntry instances are expected to have, but
134
# that don't vary for all kinds of entry. (e.g. symlink_target is only
135
# relevant to InventoryLink, so there's no reason to make every
136
# InventoryFile instance allocate space to hold a value for it.)
137
# Attributes that only vary for files: executable, text_sha1, text_size,
143
# Attributes that only vary for symlinks: symlink_target
144
symlink_target = None
145
# Attributes that only vary for tree-references: reference_revision
146
reference_revision = None
135
149
def detect_changes(self, old_entry):
136
150
"""Return a (text_modified, meta_modified) from this to old_entry.
231
229
known_kinds = ('file', 'directory', 'symlink')
233
def _put_in_tar(self, item, tree):
234
"""populate item for stashing in a tar, and return the content stream.
236
If no content is available, return None.
238
raise BzrError("don't know how to export {%s} of kind %r" %
239
(self.file_id, self.kind))
241
@deprecated_method(deprecated_in((1, 6, 0)))
242
def put_on_disk(self, dest, dp, tree):
243
"""Create a representation of self on disk in the prefix dest.
245
This is a template method - implement _put_on_disk in subclasses.
247
fullpath = osutils.pathjoin(dest, dp)
248
self._put_on_disk(fullpath, tree)
249
# mutter(" export {%s} kind %s to %s", self.file_id,
250
# self.kind, fullpath)
252
def _put_on_disk(self, fullpath, tree):
253
"""Put this entry onto disk at fullpath, from tree tree."""
254
raise BzrError("don't know how to export {%s} of kind %r" % (self.file_id, self.kind))
256
231
def sorted_children(self):
257
232
return sorted(self.children.items())
399
class RootEntry(InventoryEntry):
401
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
402
'text_id', 'parent_id', 'children', 'executable',
403
'revision', 'symlink_target', 'reference_revision']
405
def _check(self, checker, rev_id):
406
"""See InventoryEntry._check"""
408
def __init__(self, file_id):
409
self.file_id = file_id
411
self.kind = 'directory'
412
self.parent_id = None
415
symbol_versioning.warn('RootEntry is deprecated as of bzr 0.10.'
416
' Please use InventoryDirectory instead.',
417
DeprecationWarning, stacklevel=2)
419
def __eq__(self, other):
420
if not isinstance(other, RootEntry):
421
return NotImplemented
423
return (self.file_id == other.file_id) \
424
and (self.children == other.children)
427
374
class InventoryDirectory(InventoryEntry):
428
375
"""A directory in an inventory."""
430
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
431
'text_id', 'parent_id', 'children', 'executable',
432
'revision', 'symlink_target', 'reference_revision']
377
__slots__ = ['children']
434
381
def _check(self, checker, rev_id):
435
382
"""See InventoryEntry._check"""
436
if (self.text_sha1 is not None or self.text_size is not None or
437
self.text_id is not None):
438
checker._report_items.append('directory {%s} has text in revision {%s}'
439
% (self.file_id, rev_id))
440
# Directories are stored as ''.
383
# In non rich root repositories we do not expect a file graph for the
385
if self.name == '' and not checker.rich_roots:
387
# Directories are stored as an empty file, but the file should exist
388
# to provide a per-fileid log. The hash of every directory content is
389
# "da..." below (the sha1sum of '').
441
390
checker.add_pending_item(rev_id,
442
391
('texts', self.file_id, self.revision), 'text',
443
392
'da39a3ee5e6b4b0d3255bfef95601890afd80709')
452
401
def __init__(self, file_id, name, parent_id):
453
402
super(InventoryDirectory, self).__init__(file_id, name, parent_id)
454
403
self.children = {}
455
self.kind = 'directory'
457
405
def kind_character(self):
458
406
"""See InventoryEntry.kind_character."""
461
def _put_in_tar(self, item, tree):
462
"""See InventoryEntry._put_in_tar."""
463
item.type = tarfile.DIRTYPE
470
def _put_on_disk(self, fullpath, tree):
471
"""See InventoryEntry._put_on_disk."""
475
410
class InventoryFile(InventoryEntry):
476
411
"""A file in an inventory."""
478
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
479
'text_id', 'parent_id', 'children', 'executable',
480
'revision', 'symlink_target', 'reference_revision']
413
__slots__ = ['text_sha1', 'text_size', 'text_id', 'executable']
417
def __init__(self, file_id, name, parent_id):
418
super(InventoryFile, self).__init__(file_id, name, parent_id)
419
self.text_sha1 = None
420
self.text_size = None
422
self.executable = False
482
424
def _check(self, checker, tree_revision_id):
483
425
"""See InventoryEntry._check"""
526
468
"""See InventoryEntry.has_text."""
529
def __init__(self, file_id, name, parent_id):
530
super(InventoryFile, self).__init__(file_id, name, parent_id)
533
471
def kind_character(self):
534
472
"""See InventoryEntry.kind_character."""
537
def _put_in_tar(self, item, tree):
538
"""See InventoryEntry._put_in_tar."""
539
item.type = tarfile.REGTYPE
540
fileobj = tree.get_file(self.file_id)
541
item.size = self.text_size
542
if tree.is_executable(self.file_id):
548
def _put_on_disk(self, fullpath, tree):
549
"""See InventoryEntry._put_on_disk."""
550
osutils.pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
551
if tree.is_executable(self.file_id):
552
os.chmod(fullpath, 0755)
554
475
def _read_tree_state(self, path, work_tree):
555
476
"""See InventoryEntry._read_tree_state."""
556
477
self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
588
509
class InventoryLink(InventoryEntry):
589
510
"""A file in an inventory."""
591
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
592
'text_id', 'parent_id', 'children', 'executable',
593
'revision', 'symlink_target', 'reference_revision']
512
__slots__ = ['symlink_target']
516
def __init__(self, file_id, name, parent_id):
517
super(InventoryLink, self).__init__(file_id, name, parent_id)
518
self.symlink_target = None
595
520
def _check(self, checker, tree_revision_id):
596
521
"""See InventoryEntry._check"""
597
if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
598
checker._report_items.append(
599
'symlink {%s} has text in revision {%s}'
600
% (self.file_id, tree_revision_id))
601
522
if self.symlink_target is None:
602
523
checker._report_items.append(
603
524
'symlink {%s} has no target in revision {%s}'
641
562
differ = DiffSymlink(old_tree, new_tree, output_to)
642
563
return differ.diff_symlink(old_target, new_target)
644
def __init__(self, file_id, name, parent_id):
645
super(InventoryLink, self).__init__(file_id, name, parent_id)
646
self.kind = 'symlink'
648
565
def kind_character(self):
649
566
"""See InventoryEntry.kind_character."""
652
def _put_in_tar(self, item, tree):
653
"""See InventoryEntry._put_in_tar."""
654
item.type = tarfile.SYMTYPE
658
item.linkname = self.symlink_target
661
def _put_on_disk(self, fullpath, tree):
662
"""See InventoryEntry._put_on_disk."""
664
os.symlink(self.symlink_target, fullpath)
666
raise BzrError("Failed to create symlink %r -> %r, error: %s" % (fullpath, self.symlink_target, e))
668
569
def _read_tree_state(self, path, work_tree):
669
570
"""See InventoryEntry._read_tree_state."""
670
571
self.symlink_target = work_tree.get_symlink_target(self.file_id)
947
855
descend(self.root, u'')
950
def path2id(self, name):
858
def path2id(self, relpath):
951
859
"""Walk down through directories to return entry of last component.
953
names may be either a list of path components, or a single
954
string, in which case it is automatically split.
861
:param relpath: may be either a list of path components, or a single
862
string, in which case it is automatically split.
956
864
This returns the entry of the last component in the path,
957
865
which may be either a file or a directory.
959
867
Returns None IFF the path is not found.
961
if isinstance(name, basestring):
962
name = osutils.splitpath(name)
964
# mutter("lookup path %r" % name)
869
if isinstance(relpath, basestring):
870
names = osutils.splitpath(relpath)
967
875
parent = self.root
1539
1449
raise ValueError("unknown kind %r" % entry.kind)
1451
def _expand_fileids_to_parents_and_children(self, file_ids):
1452
"""Give a more wholistic view starting with the given file_ids.
1454
For any file_id which maps to a directory, we will include all children
1455
of that directory. We will also include all directories which are
1456
parents of the given file_ids, but we will not include their children.
1463
fringle # fringle-id
1467
if given [foo-id] we will include
1468
TREE_ROOT as interesting parents
1470
foo-id, baz-id, frob-id, fringle-id
1474
# TODO: Pre-pass over the list of fileids to see if anything is already
1475
# deserialized in self._fileid_to_entry_cache
1477
directories_to_expand = set()
1478
children_of_parent_id = {}
1479
# It is okay if some of the fileids are missing
1480
for entry in self._getitems(file_ids):
1481
if entry.kind == 'directory':
1482
directories_to_expand.add(entry.file_id)
1483
interesting.add(entry.parent_id)
1484
children_of_parent_id.setdefault(entry.parent_id, []
1485
).append(entry.file_id)
1487
# Now, interesting has all of the direct parents, but not the
1488
# parents of those parents. It also may have some duplicates with
1490
remaining_parents = interesting.difference(file_ids)
1491
# When we hit the TREE_ROOT, we'll get an interesting parent of None,
1492
# but we don't actually want to recurse into that
1493
interesting.add(None) # this will auto-filter it in the loop
1494
remaining_parents.discard(None)
1495
while remaining_parents:
1496
next_parents = set()
1497
for entry in self._getitems(remaining_parents):
1498
next_parents.add(entry.parent_id)
1499
children_of_parent_id.setdefault(entry.parent_id, []
1500
).append(entry.file_id)
1501
# Remove any search tips we've already processed
1502
remaining_parents = next_parents.difference(interesting)
1503
interesting.update(remaining_parents)
1504
# We should probably also .difference(directories_to_expand)
1505
interesting.update(file_ids)
1506
interesting.discard(None)
1507
while directories_to_expand:
1508
# Expand directories by looking in the
1509
# parent_id_basename_to_file_id map
1510
keys = [StaticTuple(f,).intern() for f in directories_to_expand]
1511
directories_to_expand = set()
1512
items = self.parent_id_basename_to_file_id.iteritems(keys)
1513
next_file_ids = set([item[1] for item in items])
1514
next_file_ids = next_file_ids.difference(interesting)
1515
interesting.update(next_file_ids)
1516
for entry in self._getitems(next_file_ids):
1517
if entry.kind == 'directory':
1518
directories_to_expand.add(entry.file_id)
1519
children_of_parent_id.setdefault(entry.parent_id, []
1520
).append(entry.file_id)
1521
return interesting, children_of_parent_id
1523
def filter(self, specific_fileids):
1524
"""Get an inventory view filtered against a set of file-ids.
1526
Children of directories and parents are included.
1528
The result may or may not reference the underlying inventory
1529
so it should be treated as immutable.
1532
parent_to_children) = self._expand_fileids_to_parents_and_children(
1534
# There is some overlap here, but we assume that all interesting items
1535
# are in the _fileid_to_entry_cache because we had to read them to
1536
# determine if they were a dir we wanted to recurse, or just a file
1537
# This should give us all the entries we'll want to add, so start
1539
other = Inventory(self.root_id)
1540
other.root.revision = self.root.revision
1541
other.revision_id = self.revision_id
1542
if not interesting or not parent_to_children:
1543
# empty filter, or filtering entrys that don't exist
1544
# (if even 1 existed, then we would have populated
1545
# parent_to_children with at least the tree root.)
1547
cache = self._fileid_to_entry_cache
1548
remaining_children = collections.deque(parent_to_children[self.root_id])
1549
while remaining_children:
1550
file_id = remaining_children.popleft()
1552
if ie.kind == 'directory':
1553
ie = ie.copy() # We create a copy to depopulate the .children attribute
1554
# TODO: depending on the uses of 'other' we should probably alwyas
1555
# '.copy()' to prevent someone from mutating other and
1556
# invaliding our internal cache
1558
if file_id in parent_to_children:
1559
remaining_children.extend(parent_to_children[file_id])
1542
1563
def _bytes_to_utf8name_key(bytes):
1543
1564
"""Get the file_id, revision_id key out of bytes."""
1785
1807
raise errors.BzrError('Duplicate key in inventory: %r\n%r'
1786
1808
% (key, bytes))
1787
1809
info[key] = value
1788
revision_id = info['revision_id']
1789
root_id = info['root_id']
1790
search_key_name = info.get('search_key_name', 'plain')
1791
parent_id_basename_to_file_id = info.get(
1792
'parent_id_basename_to_file_id', None)
1810
revision_id = intern(info['revision_id'])
1811
root_id = intern(info['root_id'])
1812
search_key_name = intern(info.get('search_key_name', 'plain'))
1813
parent_id_basename_to_file_id = intern(info.get(
1814
'parent_id_basename_to_file_id', None))
1815
if not parent_id_basename_to_file_id.startswith('sha1:'):
1816
raise ValueError('parent_id_basename_to_file_id should be a sha1'
1817
' key not %r' % (parent_id_basename_to_file_id,))
1793
1818
id_to_entry = info['id_to_entry']
1819
if not id_to_entry.startswith('sha1:'):
1820
raise ValueError('id_to_entry should be a sha1'
1821
' key not %r' % (id_to_entry,))
1795
1823
result = CHKInventory(search_key_name)
1796
1824
result.revision_id = revision_id
1799
1827
result._search_key_name)
1800
1828
if parent_id_basename_to_file_id is not None:
1801
1829
result.parent_id_basename_to_file_id = chk_map.CHKMap(
1802
chk_store, (parent_id_basename_to_file_id,),
1830
chk_store, StaticTuple(parent_id_basename_to_file_id,),
1803
1831
search_key_func=search_key_func)
1805
1833
result.parent_id_basename_to_file_id = None
1807
result.id_to_entry = chk_map.CHKMap(chk_store, (id_to_entry,),
1835
result.id_to_entry = chk_map.CHKMap(chk_store,
1836
StaticTuple(id_to_entry,),
1808
1837
search_key_func=search_key_func)
1809
1838
if (result.revision_id,) != expected_revision_id:
1810
1839
raise ValueError("Mismatched revision id and expected: %r, %r" %
1874
1904
return self._bytes_to_entry(
1875
self.id_to_entry.iteritems([(file_id,)]).next()[1])
1905
self.id_to_entry.iteritems([StaticTuple(file_id,)]).next()[1])
1876
1906
except StopIteration:
1877
1907
# really we're passing an inventory, not a tree...
1878
1908
raise errors.NoSuchId(self, file_id)
1910
def _getitems(self, file_ids):
1911
"""Similar to __getitem__, but lets you query for multiple.
1913
The returned order is undefined. And currently if an item doesn't
1914
exist, it isn't included in the output.
1918
for file_id in file_ids:
1919
entry = self._fileid_to_entry_cache.get(file_id, None)
1921
remaining.append(file_id)
1923
result.append(entry)
1924
file_keys = [StaticTuple(f,).intern() for f in remaining]
1925
for file_key, value in self.id_to_entry.iteritems(file_keys):
1926
entry = self._bytes_to_entry(value)
1927
result.append(entry)
1928
self._fileid_to_entry_cache[entry.file_id] = entry
1880
1931
def has_id(self, file_id):
1881
1932
# Perhaps have an explicit 'contains' method on CHKMap ?
1882
1933
if self._fileid_to_entry_cache.get(file_id, None) is not None:
1884
return len(list(self.id_to_entry.iteritems([(file_id,)]))) == 1
1936
self.id_to_entry.iteritems([StaticTuple(file_id,)]))) == 1
1886
1938
def is_root(self, file_id):
1887
1939
return file_id == self.root_id
2016
2068
delta.append((old_path, new_path, file_id, entry))
2019
def path2id(self, name):
2071
def path2id(self, relpath):
2020
2072
"""See CommonInventory.path2id()."""
2021
2073
# TODO: perhaps support negative hits?
2022
result = self._path_to_fileid_cache.get(name, None)
2074
result = self._path_to_fileid_cache.get(relpath, None)
2023
2075
if result is not None:
2025
if isinstance(name, basestring):
2026
names = osutils.splitpath(name)
2077
if isinstance(relpath, basestring):
2078
names = osutils.splitpath(relpath)
2029
2081
current_id = self.root_id
2030
2082
if current_id is None:
2032
2084
parent_id_index = self.parent_id_basename_to_file_id
2033
2086
for basename in names:
2034
# TODO: Cache each path we figure out in this function.
2087
if cur_path is None:
2090
cur_path = cur_path + '/' + basename
2035
2091
basename_utf8 = basename.encode('utf8')
2036
key_filter = [(current_id, basename_utf8)]
2038
for (parent_id, name_utf8), file_id in parent_id_index.iteritems(
2039
key_filter=key_filter):
2040
if parent_id != current_id or name_utf8 != basename_utf8:
2041
raise errors.BzrError("corrupt inventory lookup! "
2042
"%r %r %r %r" % (parent_id, current_id, name_utf8,
2092
file_id = self._path_to_fileid_cache.get(cur_path, None)
2044
2093
if file_id is None:
2094
key_filter = [StaticTuple(current_id, basename_utf8)]
2095
items = parent_id_index.iteritems(key_filter)
2096
for (parent_id, name_utf8), file_id in items:
2097
if parent_id != current_id or name_utf8 != basename_utf8:
2098
raise errors.BzrError("corrupt inventory lookup! "
2099
"%r %r %r %r" % (parent_id, current_id, name_utf8,
2104
self._path_to_fileid_cache[cur_path] = file_id
2046
2105
current_id = file_id
2047
self._path_to_fileid_cache[name] = current_id
2048
2106
return current_id
2050
2108
def to_lines(self):
2055
2113
lines.append('search_key_name: %s\n' % (self._search_key_name,))
2056
2114
lines.append("root_id: %s\n" % self.root_id)
2057
2115
lines.append('parent_id_basename_to_file_id: %s\n' %
2058
self.parent_id_basename_to_file_id.key())
2116
(self.parent_id_basename_to_file_id.key()[0],))
2059
2117
lines.append("revision_id: %s\n" % self.revision_id)
2060
lines.append("id_to_entry: %s\n" % self.id_to_entry.key())
2118
lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2062
2120
lines.append("revision_id: %s\n" % self.revision_id)
2063
2121
lines.append("root_id: %s\n" % self.root_id)
2064
2122
if self.parent_id_basename_to_file_id is not None:
2065
2123
lines.append('parent_id_basename_to_file_id: %s\n' %
2066
self.parent_id_basename_to_file_id.key())
2067
lines.append("id_to_entry: %s\n" % self.id_to_entry.key())
2124
(self.parent_id_basename_to_file_id.key()[0],))
2125
lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2076
2134
class CHKInventoryDirectory(InventoryDirectory):
2077
2135
"""A directory in an inventory."""
2079
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
2080
'text_id', 'parent_id', '_children', 'executable',
2081
'revision', 'symlink_target', 'reference_revision',
2137
__slots__ = ['_children', '_chk_inventory']
2084
2139
def __init__(self, file_id, name, parent_id, chk_inventory):
2085
2140
# Don't call InventoryDirectory.__init__ - it isn't right for this
2087
2142
InventoryEntry.__init__(self, file_id, name, parent_id)
2088
2143
self._children = None
2089
self.kind = 'directory'
2090
2144
self._chk_inventory = chk_inventory