30
31
revision as _mod_revision,
33
35
from bzrlib.decorators import needs_read_lock
34
from bzrlib.errors import BzrError, NoSuchId
36
from bzrlib.errors import BzrError, BzrCheckError, NoSuchId
35
37
from bzrlib import errors
36
from bzrlib.inventory import InventoryFile
38
from bzrlib.inventory import Inventory, InventoryFile
37
39
from bzrlib.inter import InterObject
38
40
from bzrlib.osutils import fingerprint_file
39
41
import bzrlib.revision
40
42
from bzrlib.symbol_versioning import deprecated_function, deprecated_in
41
from bzrlib.trace import note
43
from bzrlib.trace import mutter, note
44
46
class Tree(object):
194
198
The yield order (ignoring root) would be::
195
199
a, f, a/b, a/d, a/b/c, a/d/e, f/g
197
:param yield_parents: If True, yield the parents from the root leading
198
down to specific_file_ids that have been requested. This has no
199
impact if specific_file_ids is None.
201
201
return self.inventory.iter_entries_by_dir(
202
specific_file_ids=specific_file_ids, yield_parents=yield_parents)
202
specific_file_ids=specific_file_ids)
204
204
def iter_references(self):
205
if self.supports_tree_reference():
206
for path, entry in self.iter_entries_by_dir():
207
if entry.kind == 'tree-reference':
208
yield path, entry.file_id
205
for path, entry in self.iter_entries_by_dir():
206
if entry.kind == 'tree-reference':
207
yield path, entry.file_id
210
209
def kind(self, file_id):
211
210
raise NotImplementedError("Tree subclass %s must implement kind"
222
221
def path_content_summary(self, path):
223
222
"""Get a summary of the information about path.
225
All the attributes returned are for the canonical form, not the
226
convenient form (if content filters are in use.)
228
224
:param path: A relative path within the tree.
229
225
:return: A tuple containing kind, size, exec, sha1-or-link.
230
226
Kind is always present (see tree.kind()).
231
size is present if kind is file and the size of the
232
canonical form can be cheaply determined, None otherwise.
227
size is present if kind is file, None otherwise.
233
228
exec is None unless kind is file and the platform supports the 'x'
235
230
sha1-or-link is the link target if kind is symlink, or the sha1 if
268
263
raise NotImplementedError(self.get_file)
270
def get_file_with_stat(self, file_id, path=None):
271
"""Get a file handle and stat object for file_id.
273
The default implementation returns (self.get_file, None) for backwards
276
:param file_id: The file id to read.
277
:param path: The path of the file, if it is known.
278
:return: A tuple (file_handle, stat_value_or_None). If the tree has
279
no stat facility, or need for a stat cache feedback during commit,
280
it may return None for the second element of the tuple.
282
return (self.get_file(file_id, path), None)
284
265
def get_file_text(self, file_id, path=None):
285
266
"""Return the byte content of a file.
857
def _changes_from_entries(self, source_entry, target_entry,
858
source_path=None, target_path=None):
859
"""Generate a iter_changes tuple between source_entry and target_entry.
861
:param source_entry: An inventory entry from self.source, or None.
862
:param target_entry: An inventory entry from self.target, or None.
863
:param source_path: The path of source_entry, if known. If not known
864
it will be looked up.
865
:param target_path: The path of target_entry, if known. If not known
866
it will be looked up.
867
:return: A tuple, item 0 of which is an iter_changes result tuple, and
868
item 1 is True if there are any changes in the result tuple.
870
if source_entry is None:
871
if target_entry is None:
873
file_id = target_entry.file_id
875
file_id = source_entry.file_id
876
if source_entry is not None:
877
source_versioned = True
878
source_name = source_entry.name
879
source_parent = source_entry.parent_id
880
if source_path is None:
881
source_path = self.source.id2path(file_id)
882
source_kind, source_executable, source_stat = \
883
self.source._comparison_data(source_entry, source_path)
885
source_versioned = False
889
source_executable = None
890
if target_entry is not None:
891
target_versioned = True
892
target_name = target_entry.name
893
target_parent = target_entry.parent_id
894
if target_path is None:
895
target_path = self.target.id2path(file_id)
896
target_kind, target_executable, target_stat = \
897
self.target._comparison_data(target_entry, target_path)
899
target_versioned = False
903
target_executable = None
904
versioned = (source_versioned, target_versioned)
905
kind = (source_kind, target_kind)
906
changed_content = False
907
if source_kind != target_kind:
908
changed_content = True
909
elif source_kind == 'file':
910
if (self.source.get_file_sha1(file_id, source_path, source_stat) !=
911
self.target.get_file_sha1(file_id, target_path, target_stat)):
912
changed_content = True
913
elif source_kind == 'symlink':
914
if (self.source.get_symlink_target(file_id) !=
915
self.target.get_symlink_target(file_id)):
916
changed_content = True
917
# XXX: Yes, the indentation below is wrong. But fixing it broke
918
# test_merge.TestMergerEntriesLCAOnDisk.
919
# test_nested_tree_subtree_renamed_and_modified. We'll wait for
920
# the fix from bzr.dev -- vila 2009026
921
elif source_kind == 'tree-reference':
922
if (self.source.get_reference_revision(file_id, source_path)
923
!= self.target.get_reference_revision(file_id, target_path)):
924
changed_content = True
925
parent = (source_parent, target_parent)
926
name = (source_name, target_name)
927
executable = (source_executable, target_executable)
928
if (changed_content is not False or versioned[0] != versioned[1]
929
or parent[0] != parent[1] or name[0] != name[1] or
930
executable[0] != executable[1]):
934
return (file_id, (source_path, target_path), changed_content,
935
versioned, parent, name, kind, executable), changes
938
837
def compare(self, want_unchanged=False, specific_files=None,
939
838
extra_trees=None, require_versioned=False, include_root=False,
1052
933
# can be extras. So the fake_entry is solely used to look up
1053
934
# executable it values when execute is not supported.
1054
935
fake_entry = InventoryFile('unused', 'unused', 'unused')
1055
for target_path, target_entry in to_entries_by_dir:
1056
while (all_unversioned and
1057
all_unversioned[0][0] < target_path.split('/')):
936
for to_path, to_entry in to_entries_by_dir:
937
while all_unversioned and all_unversioned[0][0] < to_path.split('/'):
1058
938
unversioned_path = all_unversioned.popleft()
1059
target_kind, target_executable, target_stat = \
939
to_kind, to_executable, to_stat = \
1060
940
self.target._comparison_data(fake_entry, unversioned_path[1])
1061
941
yield (None, (None, unversioned_path[1]), True, (False, False),
1063
943
(None, unversioned_path[0][-1]),
1064
(None, target_kind),
1065
(None, target_executable))
1066
source_path, source_entry = from_data.get(target_entry.file_id,
1068
result, changes = self._changes_from_entries(source_entry,
1069
target_entry, source_path=source_path, target_path=target_path)
1070
to_paths[result[0]] = result[1][1]
945
(None, to_executable))
946
file_id = to_entry.file_id
947
to_paths[file_id] = to_path
1071
948
entry_count += 1
949
changed_content = False
950
from_path, from_entry = from_data.get(file_id, (None, None))
951
from_versioned = (from_entry is not None)
952
if from_entry is not None:
953
from_versioned = True
954
from_name = from_entry.name
955
from_parent = from_entry.parent_id
956
from_kind, from_executable, from_stat = \
957
self.source._comparison_data(from_entry, from_path)
1073
958
entry_count += 1
960
from_versioned = False
964
from_executable = None
965
versioned = (from_versioned, True)
966
to_kind, to_executable, to_stat = \
967
self.target._comparison_data(to_entry, to_path)
968
kind = (from_kind, to_kind)
969
if kind[0] != kind[1]:
970
changed_content = True
971
elif from_kind == 'file':
972
if (self.source.get_file_sha1(file_id, from_path, from_stat) !=
973
self.target.get_file_sha1(file_id, to_path, to_stat)):
974
changed_content = True
975
elif from_kind == 'symlink':
976
if (self.source.get_symlink_target(file_id) !=
977
self.target.get_symlink_target(file_id)):
978
changed_content = True
979
# XXX: Yes, the indentation below is wrong. But fixing it broke
980
# test_merge.TestMergerEntriesLCAOnDisk.
981
# test_nested_tree_subtree_renamed_and_modified. We'll wait for
982
# the fix from bzr.dev -- vila 2009026
983
elif from_kind == 'tree-reference':
984
if (self.source.get_reference_revision(file_id, from_path)
985
!= self.target.get_reference_revision(file_id, to_path)):
986
changed_content = True
987
parent = (from_parent, to_entry.parent_id)
988
name = (from_name, to_entry.name)
989
executable = (from_executable, to_executable)
1074
990
if pb is not None:
1075
991
pb.update('comparing files', entry_count, num_entries)
1076
if changes or include_unchanged:
1077
if specific_file_ids is not None:
1078
new_parent_id = result[4][1]
1079
precise_file_ids.add(new_parent_id)
1080
changed_file_ids.append(result[0])
1082
# Ensure correct behaviour for reparented/added specific files.
1083
if specific_files is not None:
1084
# Record output dirs
1085
if result[6][1] == 'directory':
1086
seen_dirs.add(result[0])
1087
# Record parents of reparented/added entries.
1088
versioned = result[3]
1090
if not versioned[0] or parents[0] != parents[1]:
1091
seen_parents.add(parents[1])
992
if (changed_content is not False or versioned[0] != versioned[1]
993
or parent[0] != parent[1] or name[0] != name[1] or
994
executable[0] != executable[1] or include_unchanged):
995
yield (file_id, (from_path, to_path), changed_content,
996
versioned, parent, name, kind, executable)
1092
998
while all_unversioned:
1093
999
# yield any trailing unversioned paths
1094
1000
unversioned_path = all_unversioned.popleft()
1099
1005
(None, unversioned_path[0][-1]),
1100
1006
(None, to_kind),
1101
1007
(None, to_executable))
1102
# Yield all remaining source paths
1009
def get_to_path(to_entry):
1010
if to_entry.parent_id is None:
1011
to_path = '' # the root
1013
if to_entry.parent_id not in to_paths:
1015
return get_to_path(self.target.inventory[to_entry.parent_id])
1016
to_path = osutils.pathjoin(to_paths[to_entry.parent_id],
1018
to_paths[to_entry.file_id] = to_path
1103
1021
for path, from_entry in from_entries_by_dir:
1104
1022
file_id = from_entry.file_id
1105
1023
if file_id in to_paths:
1106
1024
# already returned
1108
if file_id not in self.target.all_file_ids():
1026
if not file_id in self.target.all_file_ids():
1109
1027
# common case - paths we have not emitted are not present in
1113
to_path = self.target.id2path(file_id)
1031
to_path = get_to_path(self.target.inventory[file_id])
1114
1032
entry_count += 1
1115
1033
if pb is not None:
1116
1034
pb.update('comparing files', entry_count, num_entries)
1123
1041
executable = (from_executable, None)
1124
1042
changed_content = from_kind is not None
1125
1043
# the parent's path is necessarily known at this point.
1126
changed_file_ids.append(file_id)
1127
1044
yield(file_id, (path, to_path), changed_content, versioned, parent,
1128
1045
name, kind, executable)
1129
changed_file_ids = set(changed_file_ids)
1130
if specific_file_ids is not None:
1131
for result in self._handle_precise_ids(precise_file_ids,
1135
def _get_entry(self, tree, file_id):
1136
"""Get an inventory entry from a tree, with missing entries as None.
1138
If the tree raises NotImplementedError on accessing .inventory, then
1139
this is worked around using iter_entries_by_dir on just the file id
1142
:param tree: The tree to lookup the entry in.
1143
:param file_id: The file_id to lookup.
1146
inventory = tree.inventory
1147
except NotImplementedError:
1148
# No inventory available.
1150
iterator = tree.iter_entries_by_dir(specific_file_ids=[file_id])
1151
return iterator.next()[1]
1152
except StopIteration:
1156
return inventory[file_id]
1157
except errors.NoSuchId:
1160
def _handle_precise_ids(self, precise_file_ids, changed_file_ids,
1161
discarded_changes=None):
1162
"""Fill out a partial iter_changes to be consistent.
1164
:param precise_file_ids: The file ids of parents that were seen during
1166
:param changed_file_ids: The file ids of already emitted items.
1167
:param discarded_changes: An optional dict of precalculated
1168
iter_changes items which the partial iter_changes had not output
1170
:return: A generator of iter_changes items to output.
1172
# process parents of things that had changed under the users
1173
# requested paths to prevent incorrect paths or parent ids which
1174
# aren't in the tree.
1175
while precise_file_ids:
1176
precise_file_ids.discard(None)
1177
# Don't emit file_ids twice
1178
precise_file_ids.difference_update(changed_file_ids)
1179
if not precise_file_ids:
1181
# If the there was something at a given output path in source, we
1182
# have to include the entry from source in the delta, or we would
1183
# be putting this entry into a used path.
1185
for parent_id in precise_file_ids:
1187
paths.append(self.target.id2path(parent_id))
1188
except errors.NoSuchId:
1189
# This id has been dragged in from the source by delta
1190
# expansion and isn't present in target at all: we don't
1191
# need to check for path collisions on it.
1194
old_id = self.source.path2id(path)
1195
precise_file_ids.add(old_id)
1196
precise_file_ids.discard(None)
1197
current_ids = precise_file_ids
1198
precise_file_ids = set()
1199
# We have to emit all of precise_file_ids that have been altered.
1200
# We may have to output the children of some of those ids if any
1201
# directories have stopped being directories.
1202
for file_id in current_ids:
1204
if discarded_changes:
1205
result = discarded_changes.get(file_id)
1210
old_entry = self._get_entry(self.source, file_id)
1211
new_entry = self._get_entry(self.target, file_id)
1212
result, changes = self._changes_from_entries(
1213
old_entry, new_entry)
1216
# Get this parents parent to examine.
1217
new_parent_id = result[4][1]
1218
precise_file_ids.add(new_parent_id)
1220
if (result[6][0] == 'directory' and
1221
result[6][1] != 'directory'):
1222
# This stopped being a directory, the old children have
1224
if old_entry is None:
1225
# Reusing a discarded change.
1226
old_entry = self._get_entry(self.source, file_id)
1227
for child in old_entry.children.values():
1228
precise_file_ids.add(child.file_id)
1229
changed_file_ids.add(result[0])
1233
1048
class MultiWalker(object):