130
106
class TreeReference(TreeEntry):
131
107
"""See TreeEntry. This is a reference to a nested tree in a working tree."""
135
109
kind = 'tree-reference'
137
111
def kind_character(self):
141
class TreeChange(object):
142
"""Describes the changes between the same item in two different trees."""
144
__slots__ = ['file_id', 'path', 'changed_content', 'versioned', 'parent_id',
145
'name', 'kind', 'executable', 'copied']
147
def __init__(self, file_id, path, changed_content, versioned, parent_id,
148
name, kind, executable, copied=False):
149
self.file_id = file_id
151
self.changed_content = changed_content
152
self.versioned = versioned
153
self.parent_id = parent_id
156
self.executable = executable
160
return "%s%r" % (self.__class__.__name__, self._as_tuple())
163
return len(self.__slots__)
166
return (self.file_id, self.path, self.changed_content, self.versioned,
167
self.parent_id, self.name, self.kind, self.executable, self.copied)
169
def __eq__(self, other):
170
if isinstance(other, TreeChange):
171
return self._as_tuple() == other._as_tuple()
172
if isinstance(other, tuple):
173
return self._as_tuple() == other
176
def __lt__(self, other):
177
return self._as_tuple() < other._as_tuple()
179
def meta_modified(self):
180
if self.versioned == (True, True):
181
return (self.executable[0] != self.executable[1])
184
def is_reparented(self):
185
return self.parent_id[0] != self.parent_id[1]
187
def discard_new(self):
188
return self.__class__(
189
self.file_id, (self.path[0], None), self.changed_content,
190
(self.versioned[0], None), (self.parent_id[0], None),
191
(self.name[0], None), (self.kind[0], None),
192
(self.executable[0], None),
196
115
class Tree(object):
197
116
"""Abstract file tree.
310
223
"""Iterate through all paths, including paths for missing files."""
311
224
raise NotImplementedError(self.all_versioned_paths)
313
def id2path(self, file_id, recurse='down'):
226
def id2path(self, file_id):
314
227
"""Return the path for a file id.
316
229
:raises NoSuchId:
318
231
raise NotImplementedError(self.id2path)
320
def iter_entries_by_dir(self, specific_files=None, recurse_nested=False):
233
def iter_entries_by_dir(self, specific_files=None):
321
234
"""Walk the tree in 'by_dir' order.
323
236
This will yield each entry in the tree as a (path, entry) tuple.
358
267
raise NotImplementedError(self.iter_child_entries)
360
def list_files(self, include_root=False, from_dir=None, recursive=True,
361
recurse_nested=False):
269
def list_files(self, include_root=False, from_dir=None, recursive=True):
362
270
"""List all files in this tree.
364
272
:param include_root: Whether to include the entry for the tree root
365
273
:param from_dir: Directory under which to list files
366
274
:param recursive: Whether to list files recursively
367
:param recurse_nested: enter nested trees
368
275
:return: iterator over tuples of
369
276
(path, versioned, kind, inventory entry)
374
281
if self.supports_tree_reference():
375
282
for path, entry in self.iter_entries_by_dir():
376
283
if entry.kind == 'tree-reference':
379
def get_containing_nested_tree(self, path):
380
"""Find the nested tree that contains a path.
382
:return: tuple with (nested tree and path inside the nested tree)
384
for nested_path in self.iter_references():
386
if path.startswith(nested_path):
387
nested_tree = self.get_nested_tree(nested_path)
388
return nested_tree, path[len(nested_path):]
392
def get_nested_tree(self, path):
393
"""Open the nested tree at the specified path.
395
:param path: Path from which to resolve tree reference.
396
:return: A Tree object for the nested tree
397
:raise MissingNestedTree: If the nested tree can not be resolved
399
raise NotImplementedError(self.get_nested_tree)
284
yield path, entry.file_id
401
286
def kind(self, path):
402
287
raise NotImplementedError("Tree subclass %s must implement kind"
862
file_id, (source_path, target_path), changed_content,
863
versioned, parent, name, kind, executable), changes
759
return (file_id, (source_path, target_path), changed_content,
760
versioned, parent, name, kind, executable), changes
865
762
def compare(self, want_unchanged=False, specific_files=None,
866
763
extra_trees=None, require_versioned=False, include_root=False,
980
876
target_kind, target_executable, target_stat = \
981
877
self.target._comparison_data(
982
878
fake_entry, unversioned_path[1])
984
None, (None, unversioned_path[1]), True, (False, False),
986
(None, unversioned_path[0][-1]),
988
(None, target_executable))
989
source_path = path_equivs[target_path]
990
if source_path is not None:
991
source_entry = from_data.get(source_path)
994
result, changes = self._changes_from_entries(
995
source_entry, target_entry, source_path=source_path, target_path=target_path)
996
to_paths[result.file_id] = result.path[1]
879
yield (None, (None, unversioned_path[1]), True, (False, False),
881
(None, unversioned_path[0][-1]),
883
(None, target_executable))
884
source_path, source_entry = from_data.get(target_entry.file_id,
886
result, changes = self._changes_from_entries(source_entry,
887
target_entry, source_path=source_path, target_path=target_path)
888
to_paths[result[0]] = result[1][1]
998
if result.versioned[0]:
1000
892
if pb is not None:
1001
893
pb.update('comparing files', entry_count, num_entries)
1002
894
if changes or include_unchanged:
1003
895
if specific_files is not None:
1004
precise_file_ids.add(result.parent_id[1])
1005
changed_file_ids.append(result.file_id)
896
new_parent_id = result[4][1]
897
precise_file_ids.add(new_parent_id)
898
changed_file_ids.append(result[0])
1007
900
# Ensure correct behaviour for reparented/added specific files.
1008
901
if specific_files is not None:
1009
902
# Record output dirs
1010
if result.kind[1] == 'directory':
1011
seen_dirs.add(result.file_id)
903
if result[6][1] == 'directory':
904
seen_dirs.add(result[0])
1012
905
# Record parents of reparented/added entries.
1013
if not result.versioned[0] or result.is_reparented():
1014
seen_parents.add(result.parent_id[1])
906
versioned = result[3]
908
if not versioned[0] or parents[0] != parents[1]:
909
seen_parents.add(parents[1])
1015
910
while all_unversioned:
1016
911
# yield any trailing unversioned paths
1017
912
unversioned_path = all_unversioned.popleft()
1018
913
to_kind, to_executable, to_stat = \
1019
914
self.target._comparison_data(fake_entry, unversioned_path[1])
1021
None, (None, unversioned_path[1]), True, (False, False),
1023
(None, unversioned_path[0][-1]),
1025
(None, to_executable))
915
yield (None, (None, unversioned_path[1]), True, (False, False),
917
(None, unversioned_path[0][-1]),
919
(None, to_executable))
1026
920
# Yield all remaining source paths
1027
921
for path, from_entry in from_entries_by_dir:
1028
922
file_id = from_entry.file_id
1029
923
if file_id in to_paths:
1030
924
# already returned
1032
to_path = self.find_target_path(path)
926
to_path = find_previous_path(self.source, self.target, path)
1033
927
entry_count += 1
1034
928
if pb is not None:
1035
929
pb.update('comparing files', entry_count, num_entries)
1043
937
changed_content = from_kind is not None
1044
938
# the parent's path is necessarily known at this point.
1045
939
changed_file_ids.append(file_id)
1047
file_id, (path, to_path), changed_content, versioned, parent,
1048
name, kind, executable)
940
yield(file_id, (path, to_path), changed_content, versioned, parent,
941
name, kind, executable)
1049
942
changed_file_ids = set(changed_file_ids)
1050
943
if specific_files is not None:
1051
944
for result in self._handle_precise_ids(precise_file_ids,
1143
1036
# Get this parents parent to examine.
1144
new_parent_id = result.parent_id[1]
1037
new_parent_id = result[4][1]
1145
1038
precise_file_ids.add(new_parent_id)
1147
if (result.kind[0] == 'directory' and
1148
result.kind[1] != 'directory'):
1040
if (result[6][0] == 'directory' and
1041
result[6][1] != 'directory'):
1149
1042
# This stopped being a directory, the old children have
1150
1043
# to be included.
1151
1044
if source_entry is None:
1152
1045
# Reusing a discarded change.
1153
1046
source_entry = self._get_entry(
1154
self.source, result.path[0])
1047
self.source, result[1][0])
1155
1048
precise_file_ids.update(
1157
for child in self.source.iter_child_entries(result.path[0]))
1158
changed_file_ids.add(result.file_id)
1050
for child in self.source.iter_child_entries(result[1][0]))
1051
changed_file_ids.add(result[0])
1161
1054
def file_content_matches(
1183
1078
# Fall back to SHA1 for now
1184
1079
if source_verifier_kind != "SHA1":
1185
1080
source_sha1 = self.source.get_file_sha1(
1186
source_path, source_stat)
1081
source_path, source_file_id, source_stat)
1188
1083
source_sha1 = source_verifier_data
1189
1084
if target_verifier_kind != "SHA1":
1190
1085
target_sha1 = self.target.get_file_sha1(
1191
target_path, target_stat)
1086
target_path, target_file_id, target_stat)
1193
1088
target_sha1 = target_verifier_data
1194
1089
return (source_sha1 == target_sha1)
1196
def find_target_path(self, path, recurse='none'):
1197
"""Find target tree path.
1199
:param path: Path to search for (exists in source)
1200
:return: path in target, or None if there is no equivalent path.
1201
:raise NoSuchFile: If the path doesn't exist in source
1203
file_id = self.source.path2id(path)
1205
raise errors.NoSuchFile(path)
1207
return self.target.id2path(file_id, recurse=recurse)
1208
except errors.NoSuchId:
1211
def find_source_path(self, path, recurse='none'):
1212
"""Find the source tree path.
1214
:param path: Path to search for (exists in target)
1215
:return: path in source, or None if there is no equivalent path.
1216
:raise NoSuchFile: if the path doesn't exist in target
1218
file_id = self.target.path2id(path)
1220
raise errors.NoSuchFile(path)
1222
return self.source.id2path(file_id, recurse=recurse)
1223
except errors.NoSuchId:
1226
def find_target_paths(self, paths, recurse='none'):
1227
"""Find target tree paths.
1229
:param paths: Iterable over paths in target to search for
1230
:return: Dictionary mapping from source paths to paths in target , or
1231
None if there is no equivalent path.
1235
ret[path] = self.find_target_path(path, recurse=recurse)
1238
def find_source_paths(self, paths, recurse='none'):
1239
"""Find source tree paths.
1241
:param paths: Iterable over paths in target to search for
1242
:return: Dictionary mapping from target paths to paths in source, or
1243
None if there is no equivalent path.
1247
ret[path] = self.find_source_path(path, recurse=recurse)
1251
1092
InterTree.register_optimiser(InterTree)
1254
def find_previous_paths(from_tree, to_tree, paths, recurse='none'):
1095
def find_previous_paths(from_tree, to_tree, paths):
1255
1096
"""Find previous tree paths.
1257
1098
:param from_tree: From tree
1258
1099
:param to_tree: To tree
1259
:param paths: Iterable over paths in from_tree to search for
1100
:param paths: Iterable over paths to search for
1260
1101
:return: Dictionary mapping from from_tree paths to paths in to_tree, or
1261
1102
None if there is no equivalent path.
1263
return InterTree.get(to_tree, from_tree).find_source_paths(paths, recurse=recurse)
1266
def find_previous_path(from_tree, to_tree, path, recurse='none'):
1106
ret[path] = find_previous_path(from_tree, to_tree, path)
1110
def find_previous_path(from_tree, to_tree, path, file_id=None):
1267
1111
"""Find previous tree path.
1269
1113
:param from_tree: From tree
1270
1114
:param to_tree: To tree
1271
:param path: Path to search for (exists in from_tree)
1115
:param path: Path to search for
1272
1116
:return: path in to_tree, or None if there is no equivalent path.
1273
:raise NoSuchFile: If the path doesn't exist in from_tree
1275
return InterTree.get(to_tree, from_tree).find_source_path(
1276
path, recurse=recurse)
1119
file_id = from_tree.path2id(path)
1121
raise errors.NoSuchFile(path)
1123
return to_tree.id2path(file_id)
1124
except errors.NoSuchId:
1279
1128
def get_canonical_path(tree, path, normalize):