1
# Copyright (C) 2005-2010 Canonical Ltd
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
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
128
130
RENAMED = 'renamed'
129
131
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
149
135
def detect_changes(self, old_entry):
150
136
"""Return a (text_modified, meta_modified) from this to old_entry.
189
175
candidates[ie.revision] = ie
190
176
return candidates
178
@deprecated_method(deprecated_in((1, 6, 0)))
179
def get_tar_item(self, root, dp, now, tree):
180
"""Get a tarfile item and a file stream for its content."""
181
item = tarfile.TarInfo(osutils.pathjoin(root, dp).encode('utf8'))
182
# TODO: would be cool to actually set it to the timestamp of the
183
# revision it was last changed
185
fileobj = self._put_in_tar(item, tree)
192
188
def has_text(self):
193
189
"""Return true if the object this entry represents has textual data.
203
def __init__(self, file_id, name, parent_id):
199
def __init__(self, file_id, name, parent_id, text_id=None):
204
200
"""Create an InventoryEntry
206
202
The filename must be a single component, relative to the
218
214
if '/' in name or '\\' in name:
219
215
raise errors.InvalidEntryName(name=name)
216
self.executable = False
218
self.text_sha1 = None
219
self.text_size = None
220
220
self.file_id = file_id
222
self.text_id = text_id
223
223
self.parent_id = parent_id
224
self.symlink_target = None
225
self.reference_revision = None
225
227
def kind_character(self):
226
228
"""Return a short kind indicator useful for appending to names."""
229
231
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))
231
256
def sorted_children(self):
232
257
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)
374
427
class InventoryDirectory(InventoryEntry):
375
428
"""A directory in an inventory."""
377
__slots__ = ['children']
430
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
431
'text_id', 'parent_id', 'children', 'executable',
432
'revision', 'symlink_target', 'reference_revision']
381
434
def _check(self, checker, rev_id):
382
435
"""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))
383
440
# In non rich root repositories we do not expect a file graph for the
385
442
if self.name == '' and not checker.rich_roots:
401
458
def __init__(self, file_id, name, parent_id):
402
459
super(InventoryDirectory, self).__init__(file_id, name, parent_id)
403
460
self.children = {}
461
self.kind = 'directory'
405
463
def kind_character(self):
406
464
"""See InventoryEntry.kind_character."""
467
def _put_in_tar(self, item, tree):
468
"""See InventoryEntry._put_in_tar."""
469
item.type = tarfile.DIRTYPE
476
def _put_on_disk(self, fullpath, tree):
477
"""See InventoryEntry._put_on_disk."""
410
481
class InventoryFile(InventoryEntry):
411
482
"""A file in an inventory."""
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
484
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
485
'text_id', 'parent_id', 'children', 'executable',
486
'revision', 'symlink_target', 'reference_revision']
424
488
def _check(self, checker, tree_revision_id):
425
489
"""See InventoryEntry._check"""
468
532
"""See InventoryEntry.has_text."""
535
def __init__(self, file_id, name, parent_id):
536
super(InventoryFile, self).__init__(file_id, name, parent_id)
471
539
def kind_character(self):
472
540
"""See InventoryEntry.kind_character."""
543
def _put_in_tar(self, item, tree):
544
"""See InventoryEntry._put_in_tar."""
545
item.type = tarfile.REGTYPE
546
fileobj = tree.get_file(self.file_id)
547
item.size = self.text_size
548
if tree.is_executable(self.file_id):
554
def _put_on_disk(self, fullpath, tree):
555
"""See InventoryEntry._put_on_disk."""
556
osutils.pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
557
if tree.is_executable(self.file_id):
558
os.chmod(fullpath, 0755)
475
560
def _read_tree_state(self, path, work_tree):
476
561
"""See InventoryEntry._read_tree_state."""
477
562
self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
509
594
class InventoryLink(InventoryEntry):
510
595
"""A file in an inventory."""
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
597
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
598
'text_id', 'parent_id', 'children', 'executable',
599
'revision', 'symlink_target', 'reference_revision']
520
601
def _check(self, checker, tree_revision_id):
521
602
"""See InventoryEntry._check"""
603
if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
604
checker._report_items.append(
605
'symlink {%s} has text in revision {%s}'
606
% (self.file_id, tree_revision_id))
522
607
if self.symlink_target is None:
523
608
checker._report_items.append(
524
609
'symlink {%s} has no target in revision {%s}'
562
647
differ = DiffSymlink(old_tree, new_tree, output_to)
563
648
return differ.diff_symlink(old_target, new_target)
650
def __init__(self, file_id, name, parent_id):
651
super(InventoryLink, self).__init__(file_id, name, parent_id)
652
self.kind = 'symlink'
565
654
def kind_character(self):
566
655
"""See InventoryEntry.kind_character."""
658
def _put_in_tar(self, item, tree):
659
"""See InventoryEntry._put_in_tar."""
660
item.type = tarfile.SYMTYPE
664
item.linkname = self.symlink_target
667
def _put_on_disk(self, fullpath, tree):
668
"""See InventoryEntry._put_on_disk."""
670
os.symlink(self.symlink_target, fullpath)
672
raise BzrError("Failed to create symlink %r -> %r, error: %s" % (fullpath, self.symlink_target, e))
569
674
def _read_tree_state(self, path, work_tree):
570
675
"""See InventoryEntry._read_tree_state."""
571
676
self.symlink_target = work_tree.get_symlink_target(self.file_id)
584
689
class TreeReference(InventoryEntry):
586
__slots__ = ['reference_revision']
588
691
kind = 'tree-reference'
590
693
def __init__(self, file_id, name, parent_id, revision=None,
856
958
descend(self.root, u'')
859
def path2id(self, relpath):
961
def path2id(self, name):
860
962
"""Walk down through directories to return entry of last component.
862
:param relpath: may be either a list of path components, or a single
863
string, in which case it is automatically split.
964
names may be either a list of path components, or a single
965
string, in which case it is automatically split.
865
967
This returns the entry of the last component in the path,
866
968
which may be either a file or a directory.
868
970
Returns None IFF the path is not found.
870
if isinstance(relpath, basestring):
871
names = osutils.splitpath(relpath)
972
if isinstance(name, basestring):
973
name = osutils.splitpath(name)
975
# mutter("lookup path %r" % name)
876
978
parent = self.root
1494
1599
interesting.add(None) # this will auto-filter it in the loop
1495
1600
remaining_parents.discard(None)
1496
1601
while remaining_parents:
1602
if None in remaining_parents:
1603
import pdb; pdb.set_trace()
1497
1604
next_parents = set()
1498
1605
for entry in self._getitems(remaining_parents):
1499
1606
next_parents.add(entry.parent_id)
1508
1615
while directories_to_expand:
1509
1616
# Expand directories by looking in the
1510
1617
# parent_id_basename_to_file_id map
1511
keys = [StaticTuple(f,).intern() for f in directories_to_expand]
1618
keys = [(f,) for f in directories_to_expand]
1512
1619
directories_to_expand = set()
1513
1620
items = self.parent_id_basename_to_file_id.iteritems(keys)
1514
1621
next_file_ids = set([item[1] for item in items])
1546
1653
# parent_to_children with at least the tree root.)
1548
1655
cache = self._fileid_to_entry_cache
1549
remaining_children = collections.deque(parent_to_children[self.root_id])
1657
remaining_children = collections.deque(parent_to_children[self.root_id])
1659
import pdb; pdb.set_trace()
1550
1661
while remaining_children:
1551
1662
file_id = remaining_children.popleft()
1552
1663
ie = cache[file_id]
1567
1678
# to filter out empty names because of non rich-root...
1568
1679
sections = bytes.split('\n')
1569
1680
kind, file_id = sections[0].split(': ')
1570
return (sections[2], intern(file_id), intern(sections[3]))
1681
return (sections[2], file_id, sections[3])
1572
1683
def _bytes_to_entry(self, bytes):
1573
1684
"""Deserialise a serialised entry."""
1595
1706
result.reference_revision = sections[4]
1597
1708
raise ValueError("Not a serialised entry %r" % bytes)
1598
result.file_id = intern(result.file_id)
1599
result.revision = intern(sections[3])
1709
result.revision = sections[3]
1600
1710
if result.parent_id == '':
1601
1711
result.parent_id = None
1602
1712
self._fileid_to_entry_cache[result.file_id] = result
1709
1819
if old_path is None:
1712
old_key = StaticTuple(file_id,)
1822
old_key = (file_id,)
1713
1823
if self.id2path(file_id) != old_path:
1714
1824
raise errors.InconsistentDelta(old_path, file_id,
1715
1825
"Entry was at wrong other path %r." %
1716
1826
self.id2path(file_id))
1717
1827
altered.add(file_id)
1718
id_to_entry_delta.append(StaticTuple(old_key, new_key, new_value))
1828
id_to_entry_delta.append((old_key, new_key, new_value))
1719
1829
if result.parent_id_basename_to_file_id is not None:
1720
1830
# parent_id, basename changes
1721
1831
if old_path is None:
1808
1918
raise errors.BzrError('Duplicate key in inventory: %r\n%r'
1809
1919
% (key, bytes))
1810
1920
info[key] = value
1811
revision_id = intern(info['revision_id'])
1812
root_id = intern(info['root_id'])
1813
search_key_name = intern(info.get('search_key_name', 'plain'))
1814
parent_id_basename_to_file_id = intern(info.get(
1815
'parent_id_basename_to_file_id', None))
1816
if not parent_id_basename_to_file_id.startswith('sha1:'):
1817
raise ValueError('parent_id_basename_to_file_id should be a sha1'
1818
' key not %r' % (parent_id_basename_to_file_id,))
1921
revision_id = info['revision_id']
1922
root_id = info['root_id']
1923
search_key_name = info.get('search_key_name', 'plain')
1924
parent_id_basename_to_file_id = info.get(
1925
'parent_id_basename_to_file_id', None)
1819
1926
id_to_entry = info['id_to_entry']
1820
if not id_to_entry.startswith('sha1:'):
1821
raise ValueError('id_to_entry should be a sha1'
1822
' key not %r' % (id_to_entry,))
1824
1928
result = CHKInventory(search_key_name)
1825
1929
result.revision_id = revision_id
1828
1932
result._search_key_name)
1829
1933
if parent_id_basename_to_file_id is not None:
1830
1934
result.parent_id_basename_to_file_id = chk_map.CHKMap(
1831
chk_store, StaticTuple(parent_id_basename_to_file_id,),
1935
chk_store, (parent_id_basename_to_file_id,),
1832
1936
search_key_func=search_key_func)
1834
1938
result.parent_id_basename_to_file_id = None
1836
result.id_to_entry = chk_map.CHKMap(chk_store,
1837
StaticTuple(id_to_entry,),
1940
result.id_to_entry = chk_map.CHKMap(chk_store, (id_to_entry,),
1838
1941
search_key_func=search_key_func)
1839
1942
if (result.revision_id,) != expected_revision_id:
1840
1943
raise ValueError("Mismatched revision id and expected: %r, %r" %
1862
1965
id_to_entry_dict = {}
1863
1966
parent_id_basename_dict = {}
1864
1967
for path, entry in inventory.iter_entries():
1865
key = StaticTuple(entry.file_id,).intern()
1866
id_to_entry_dict[key] = entry_to_bytes(entry)
1968
id_to_entry_dict[(entry.file_id,)] = entry_to_bytes(entry)
1867
1969
p_id_key = parent_id_basename_key(entry)
1868
1970
parent_id_basename_dict[p_id_key] = entry.file_id
1892
1994
parent_id = entry.parent_id
1895
return StaticTuple(parent_id, entry.name.encode('utf8')).intern()
1997
return parent_id, entry.name.encode('utf8')
1897
1999
def __getitem__(self, file_id):
1898
2000
"""map a single file_id -> InventoryEntry."""
1905
2007
return self._bytes_to_entry(
1906
self.id_to_entry.iteritems([StaticTuple(file_id,)]).next()[1])
2008
self.id_to_entry.iteritems([(file_id,)]).next()[1])
1907
2009
except StopIteration:
1908
2010
# really we're passing an inventory, not a tree...
1909
2011
raise errors.NoSuchId(self, file_id)
1922
2024
remaining.append(file_id)
1924
2026
result.append(entry)
1925
file_keys = [StaticTuple(f,).intern() for f in remaining]
2027
file_keys = [(f,) for f in remaining]
1926
2028
for file_key, value in self.id_to_entry.iteritems(file_keys):
1927
2029
entry = self._bytes_to_entry(value)
1928
2030
result.append(entry)
1933
2035
# Perhaps have an explicit 'contains' method on CHKMap ?
1934
2036
if self._fileid_to_entry_cache.get(file_id, None) is not None:
1937
self.id_to_entry.iteritems([StaticTuple(file_id,)]))) == 1
2038
return len(list(self.id_to_entry.iteritems([(file_id,)]))) == 1
1939
2040
def is_root(self, file_id):
1940
2041
return file_id == self.root_id
2069
2170
delta.append((old_path, new_path, file_id, entry))
2072
def path2id(self, relpath):
2173
def path2id(self, name):
2073
2174
"""See CommonInventory.path2id()."""
2074
2175
# TODO: perhaps support negative hits?
2075
result = self._path_to_fileid_cache.get(relpath, None)
2176
result = self._path_to_fileid_cache.get(name, None)
2076
2177
if result is not None:
2078
if isinstance(relpath, basestring):
2079
names = osutils.splitpath(relpath)
2179
if isinstance(name, basestring):
2180
names = osutils.splitpath(name)
2082
2183
current_id = self.root_id
2083
2184
if current_id is None:
2085
2186
parent_id_index = self.parent_id_basename_to_file_id
2087
2187
for basename in names:
2088
if cur_path is None:
2091
cur_path = cur_path + '/' + basename
2188
# TODO: Cache each path we figure out in this function.
2092
2189
basename_utf8 = basename.encode('utf8')
2093
file_id = self._path_to_fileid_cache.get(cur_path, None)
2190
key_filter = [(current_id, basename_utf8)]
2192
for (parent_id, name_utf8), file_id in parent_id_index.iteritems(
2193
key_filter=key_filter):
2194
if parent_id != current_id or name_utf8 != basename_utf8:
2195
raise errors.BzrError("corrupt inventory lookup! "
2196
"%r %r %r %r" % (parent_id, current_id, name_utf8,
2094
2198
if file_id is None:
2095
key_filter = [StaticTuple(current_id, basename_utf8)]
2096
items = parent_id_index.iteritems(key_filter)
2097
for (parent_id, name_utf8), file_id in items:
2098
if parent_id != current_id or name_utf8 != basename_utf8:
2099
raise errors.BzrError("corrupt inventory lookup! "
2100
"%r %r %r %r" % (parent_id, current_id, name_utf8,
2105
self._path_to_fileid_cache[cur_path] = file_id
2106
2200
current_id = file_id
2201
self._path_to_fileid_cache[name] = current_id
2107
2202
return current_id
2109
2204
def to_lines(self):
2114
2209
lines.append('search_key_name: %s\n' % (self._search_key_name,))
2115
2210
lines.append("root_id: %s\n" % self.root_id)
2116
2211
lines.append('parent_id_basename_to_file_id: %s\n' %
2117
(self.parent_id_basename_to_file_id.key()[0],))
2212
self.parent_id_basename_to_file_id.key())
2118
2213
lines.append("revision_id: %s\n" % self.revision_id)
2119
lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2214
lines.append("id_to_entry: %s\n" % self.id_to_entry.key())
2121
2216
lines.append("revision_id: %s\n" % self.revision_id)
2122
2217
lines.append("root_id: %s\n" % self.root_id)
2123
2218
if self.parent_id_basename_to_file_id is not None:
2124
2219
lines.append('parent_id_basename_to_file_id: %s\n' %
2125
(self.parent_id_basename_to_file_id.key()[0],))
2126
lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2220
self.parent_id_basename_to_file_id.key())
2221
lines.append("id_to_entry: %s\n" % self.id_to_entry.key())
2135
2230
class CHKInventoryDirectory(InventoryDirectory):
2136
2231
"""A directory in an inventory."""
2138
__slots__ = ['_children', '_chk_inventory']
2233
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
2234
'text_id', 'parent_id', '_children', 'executable',
2235
'revision', 'symlink_target', 'reference_revision',
2140
2238
def __init__(self, file_id, name, parent_id, chk_inventory):
2141
2239
# Don't call InventoryDirectory.__init__ - it isn't right for this
2143
2241
InventoryEntry.__init__(self, file_id, name, parent_id)
2144
2242
self._children = None
2243
self.kind = 'directory'
2145
2244
self._chk_inventory = chk_inventory
2166
2265
parent_id_index = self._chk_inventory.parent_id_basename_to_file_id
2167
2266
child_keys = set()
2168
2267
for (parent_id, name_utf8), file_id in parent_id_index.iteritems(
2169
key_filter=[StaticTuple(self.file_id,)]):
2170
child_keys.add(StaticTuple(file_id,))
2268
key_filter=[(self.file_id,)]):
2269
child_keys.add((file_id,))
2172
2271
for file_id_key in child_keys:
2173
2272
entry = self._chk_inventory._fileid_to_entry_cache.get(