853
def _changes_from_entries(self, source_entry, target_entry,
854
source_path=None, target_path=None):
855
"""Generate a iter_changes tuple between source_entry and target_entry.
857
:param source_entry: An inventory entry from self.source, or None.
858
:param target_entry: An inventory entry from self.target, or None.
859
:param source_path: The path of source_entry, if known. If not known
860
it will be looked up.
861
:param target_path: The path of target_entry, if known. If not known
862
it will be looked up.
863
:return: A tuple, item 0 of which is an iter_changes result tuple, and
864
item 1 is True if there are any changes in the result tuple.
866
if source_entry is None:
867
if target_entry is None:
869
file_id = target_entry.file_id
871
file_id = source_entry.file_id
872
if source_entry is not None:
873
source_versioned = True
874
source_name = source_entry.name
875
source_parent = source_entry.parent_id
876
if source_path is None:
877
source_path = self.source.id2path(file_id)
878
source_kind, source_executable, source_stat = \
879
self.source._comparison_data(source_entry, source_path)
881
source_versioned = False
885
source_executable = None
886
if target_entry is not None:
887
target_versioned = True
888
target_name = target_entry.name
889
target_parent = target_entry.parent_id
890
if target_path is None:
891
target_path = self.target.id2path(file_id)
892
target_kind, target_executable, target_stat = \
893
self.target._comparison_data(target_entry, target_path)
895
target_versioned = False
899
target_executable = None
900
versioned = (source_versioned, target_versioned)
901
kind = (source_kind, target_kind)
902
changed_content = False
903
if source_kind != target_kind:
904
changed_content = True
905
elif source_kind == 'file':
906
if (self.source.get_file_sha1(file_id, source_path, source_stat) !=
907
self.target.get_file_sha1(file_id, target_path, target_stat)):
908
changed_content = True
909
elif source_kind == 'symlink':
910
if (self.source.get_symlink_target(file_id) !=
911
self.target.get_symlink_target(file_id)):
912
changed_content = True
913
# XXX: Yes, the indentation below is wrong. But fixing it broke
914
# test_merge.TestMergerEntriesLCAOnDisk.
915
# test_nested_tree_subtree_renamed_and_modified. We'll wait for
916
# the fix from bzr.dev -- vila 2009026
917
elif source_kind == 'tree-reference':
918
if (self.source.get_reference_revision(file_id, source_path)
919
!= self.target.get_reference_revision(file_id, target_path)):
920
changed_content = True
921
parent = (source_parent, target_parent)
922
name = (source_name, target_name)
923
executable = (source_executable, target_executable)
924
if (changed_content is not False or versioned[0] != versioned[1]
925
or parent[0] != parent[1] or name[0] != name[1] or
926
executable[0] != executable[1]):
930
return (file_id, (source_path, target_path), changed_content,
931
versioned, parent, name, kind, executable), changes
850
934
def compare(self, want_unchanged=False, specific_files=None,
851
935
extra_trees=None, require_versioned=False, include_root=False,
946
1048
# can be extras. So the fake_entry is solely used to look up
947
1049
# executable it values when execute is not supported.
948
1050
fake_entry = InventoryFile('unused', 'unused', 'unused')
949
for to_path, to_entry in to_entries_by_dir:
950
while all_unversioned and all_unversioned[0][0] < to_path.split('/'):
1051
for target_path, target_entry in to_entries_by_dir:
1052
while (all_unversioned and
1053
all_unversioned[0][0] < target_path.split('/')):
951
1054
unversioned_path = all_unversioned.popleft()
952
to_kind, to_executable, to_stat = \
1055
target_kind, target_executable, target_stat = \
953
1056
self.target._comparison_data(fake_entry, unversioned_path[1])
954
1057
yield (None, (None, unversioned_path[1]), True, (False, False),
956
1059
(None, unversioned_path[0][-1]),
958
(None, to_executable))
959
file_id = to_entry.file_id
960
to_paths[file_id] = to_path
1060
(None, target_kind),
1061
(None, target_executable))
1062
source_path, source_entry = from_data.get(target_entry.file_id,
1064
result, changes = self._changes_from_entries(source_entry,
1065
target_entry, source_path=source_path, target_path=target_path)
1066
to_paths[result[0]] = result[1][1]
961
1067
entry_count += 1
962
changed_content = False
963
from_path, from_entry = from_data.get(file_id, (None, None))
964
from_versioned = (from_entry is not None)
965
if from_entry is not None:
966
from_versioned = True
967
from_name = from_entry.name
968
from_parent = from_entry.parent_id
969
from_kind, from_executable, from_stat = \
970
self.source._comparison_data(from_entry, from_path)
971
1069
entry_count += 1
973
from_versioned = False
977
from_executable = None
978
versioned = (from_versioned, True)
979
to_kind, to_executable, to_stat = \
980
self.target._comparison_data(to_entry, to_path)
981
kind = (from_kind, to_kind)
982
if kind[0] != kind[1]:
983
changed_content = True
984
elif from_kind == 'file':
985
if (self.source.get_file_sha1(file_id, from_path, from_stat) !=
986
self.target.get_file_sha1(file_id, to_path, to_stat)):
987
changed_content = True
988
elif from_kind == 'symlink':
989
if (self.source.get_symlink_target(file_id) !=
990
self.target.get_symlink_target(file_id)):
991
changed_content = True
992
# XXX: Yes, the indentation below is wrong. But fixing it broke
993
# test_merge.TestMergerEntriesLCAOnDisk.
994
# test_nested_tree_subtree_renamed_and_modified. We'll wait for
995
# the fix from bzr.dev -- vila 2009026
996
elif from_kind == 'tree-reference':
997
if (self.source.get_reference_revision(file_id, from_path)
998
!= self.target.get_reference_revision(file_id, to_path)):
999
changed_content = True
1000
parent = (from_parent, to_entry.parent_id)
1001
name = (from_name, to_entry.name)
1002
executable = (from_executable, to_executable)
1003
1070
if pb is not None:
1004
1071
pb.update('comparing files', entry_count, num_entries)
1005
if (changed_content is not False or versioned[0] != versioned[1]
1006
or parent[0] != parent[1] or name[0] != name[1] or
1007
executable[0] != executable[1] or include_unchanged):
1008
yield (file_id, (from_path, to_path), changed_content,
1009
versioned, parent, name, kind, executable)
1072
if changes or include_unchanged:
1073
if specific_file_ids is not None:
1074
new_parent_id = result[4][1]
1075
precise_file_ids.add(new_parent_id)
1076
changed_file_ids.append(result[0])
1078
# Ensure correct behaviour for reparented/added specific files.
1079
if specific_files is not None:
1080
# Record output dirs
1081
if result[6][1] == 'directory':
1082
seen_dirs.add(result[0])
1083
# Record parents of reparented/added entries.
1084
versioned = result[3]
1086
if not versioned[0] or parents[0] != parents[1]:
1087
seen_parents.add(parents[1])
1011
1088
while all_unversioned:
1012
1089
# yield any trailing unversioned paths
1013
1090
unversioned_path = all_unversioned.popleft()
1018
1095
(None, unversioned_path[0][-1]),
1019
1096
(None, to_kind),
1020
1097
(None, to_executable))
1022
def get_to_path(to_entry):
1023
if to_entry.parent_id is None:
1024
to_path = '' # the root
1026
if to_entry.parent_id not in to_paths:
1028
return get_to_path(self.target.inventory[to_entry.parent_id])
1029
to_path = osutils.pathjoin(to_paths[to_entry.parent_id],
1031
to_paths[to_entry.file_id] = to_path
1098
# Yield all remaining source paths
1034
1099
for path, from_entry in from_entries_by_dir:
1035
1100
file_id = from_entry.file_id
1036
1101
if file_id in to_paths:
1037
1102
# already returned
1039
if not file_id in self.target.all_file_ids():
1104
if file_id not in self.target.all_file_ids():
1040
1105
# common case - paths we have not emitted are not present in
1044
to_path = get_to_path(self.target.inventory[file_id])
1109
to_path = self.target.id2path(file_id)
1045
1110
entry_count += 1
1046
1111
if pb is not None:
1047
1112
pb.update('comparing files', entry_count, num_entries)
1054
1119
executable = (from_executable, None)
1055
1120
changed_content = from_kind is not None
1056
1121
# the parent's path is necessarily known at this point.
1122
changed_file_ids.append(file_id)
1057
1123
yield(file_id, (path, to_path), changed_content, versioned, parent,
1058
1124
name, kind, executable)
1125
changed_file_ids = set(changed_file_ids)
1126
if specific_file_ids is not None:
1127
for result in self._handle_precise_ids(precise_file_ids,
1131
def _get_entry(self, tree, file_id):
1132
"""Get an inventory entry from a tree, with missing entries as None.
1134
If the tree raises NotImplementedError on accessing .inventory, then
1135
this is worked around using iter_entries_by_dir on just the file id
1138
:param tree: The tree to lookup the entry in.
1139
:param file_id: The file_id to lookup.
1142
inventory = tree.inventory
1143
except NotImplementedError:
1144
# No inventory available.
1146
iterator = tree.iter_entries_by_dir(specific_file_ids=[file_id])
1147
return iterator.next()[1]
1148
except StopIteration:
1152
return inventory[file_id]
1153
except errors.NoSuchId:
1156
def _handle_precise_ids(self, precise_file_ids, changed_file_ids,
1157
discarded_changes=None):
1158
"""Fill out a partial iter_changes to be consistent.
1160
:param precise_file_ids: The file ids of parents that were seen during
1162
:param changed_file_ids: The file ids of already emitted items.
1163
:param discarded_changes: An optional dict of precalculated
1164
iter_changes items which the partial iter_changes had not output
1166
:return: A generator of iter_changes items to output.
1168
# process parents of things that had changed under the users
1169
# requested paths to prevent incorrect paths or parent ids which
1170
# aren't in the tree.
1171
while precise_file_ids:
1172
precise_file_ids.discard(None)
1173
# Don't emit file_ids twice
1174
precise_file_ids.difference_update(changed_file_ids)
1175
if not precise_file_ids:
1177
# If the there was something at a given output path in source, we
1178
# have to include the entry from source in the delta, or we would
1179
# be putting this entry into a used path.
1181
for parent_id in precise_file_ids:
1183
paths.append(self.target.id2path(parent_id))
1184
except errors.NoSuchId:
1185
# This id has been dragged in from the source by delta
1186
# expansion and isn't present in target at all: we don't
1187
# need to check for path collisions on it.
1190
old_id = self.source.path2id(path)
1191
precise_file_ids.add(old_id)
1192
precise_file_ids.discard(None)
1193
current_ids = precise_file_ids
1194
precise_file_ids = set()
1195
# We have to emit all of precise_file_ids that have been altered.
1196
# We may have to output the children of some of those ids if any
1197
# directories have stopped being directories.
1198
for file_id in current_ids:
1200
if discarded_changes:
1201
result = discarded_changes.get(file_id)
1206
old_entry = self._get_entry(self.source, file_id)
1207
new_entry = self._get_entry(self.target, file_id)
1208
result, changes = self._changes_from_entries(
1209
old_entry, new_entry)
1213
# Get this parents parent.
1214
new_parent_id = result[4][1]
1215
precise_file_ids.add(new_parent_id)
1216
if (result[6][0] == 'directory' and
1217
result[6][1] != 'directory'):
1218
# This stopped being a directory, the old children have
1220
if old_entry is None:
1221
# Reusing a discarded change.
1222
old_entry = self._get_entry(self.source, file_id)
1223
for child in old_entry.children.values():
1224
precise_file_ids.add(child.file_id)
1225
changed_file_ids.add(result[0])
1061
1229
class MultiWalker(object):