3167
3167
__slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id",
3168
3168
"last_source_parent", "last_target_parent", "include_unchanged",
3169
"use_filesystem_for_exec", "utf8_decode", "searched_specific_files",
3170
"search_specific_files", "state", "source_index", "target_index",
3171
"want_unversioned", "tree"]
3169
"partial", "use_filesystem_for_exec", "utf8_decode",
3170
"searched_specific_files", "search_specific_files",
3171
"searched_exact_paths", "search_specific_file_parents", "seen_ids",
3172
"state", "source_index", "target_index", "want_unversioned", "tree"]
3173
3174
def __init__(self, include_unchanged, use_filesystem_for_exec,
3174
3175
search_specific_files, state, source_index, target_index,
3175
3176
want_unversioned, tree):
3176
3177
self.old_dirname_to_file_id = {}
3177
3178
self.new_dirname_to_file_id = {}
3179
# Are we doing a partial iter_changes?
3180
self.partial = search_specific_files != set([''])
3178
3181
# Using a list so that we can access the values and change them in
3179
3182
# nested scope. Each one is [path, file_id, entry]
3180
3183
self.last_source_parent = [None, None]
3183
3186
self.use_filesystem_for_exec = use_filesystem_for_exec
3184
3187
self.utf8_decode = cache_utf8._utf8_decode
3185
3188
# for all search_indexs in each path at or under each element of
3186
# search_specific_files, if the detail is relocated: add the id, and add the
3187
# relocated path as one to search if its not searched already. If the
3188
# detail is not relocated, add the id.
3189
# search_specific_files, if the detail is relocated: add the id, and
3190
# add the relocated path as one to search if its not searched already.
3191
# If the detail is not relocated, add the id.
3189
3192
self.searched_specific_files = set()
3193
# When we search exact paths without expanding downwards, we record
3195
self.searched_exact_paths = set()
3190
3196
self.search_specific_files = search_specific_files
3197
# The parents up to the root of the paths we are searching.
3198
# After all normal paths are returned, these specific items are returned.
3199
self.search_specific_file_parents = set()
3200
# The ids we've sent out in the delta.
3201
self.seen_ids = set()
3191
3202
self.state = state
3192
3203
self.source_index = source_index
3193
3204
self.target_index = target_index
3205
if target_index != 0:
3206
# A lot of code in here depends on target_index == 0
3207
raise errors.BzrError('unsupported target index')
3194
3208
self.want_unversioned = want_unversioned
3195
3209
self.tree = tree
3198
3212
"""Compare an entry and real disk to generate delta information.
3200
3214
:param path_info: top_relpath, basename, kind, lstat, abspath for
3201
the path of entry. If None, then the path is considered absent.
3202
(Perhaps we should pass in a concrete entry for this ?)
3215
the path of entry. If None, then the path is considered absent in
3216
the target (Perhaps we should pass in a concrete entry for this ?)
3203
3217
Basename is returned as a utf8 string because we expect this
3204
3218
tuple will be ignored, and don't want to take the time to
3206
3220
:return: (iter_changes_result, changed). If the entry has not been
3207
3221
handled then changed is None. Otherwise it is False if no content
3208
or metadata changes have occured, and None if any content or
3209
metadata change has occured. If self.include_unchanged is True then
3222
or metadata changes have occurred, and True if any content or
3223
metadata change has occurred. If self.include_unchanged is True then
3210
3224
if changed is not None, iter_changes_result will always be a result
3211
3225
tuple. Otherwise, iter_changes_result is None unless changed is
3795
3838
current_dir_info = dir_iterator.next()
3796
3839
except StopIteration:
3797
3840
current_dir_info = None
3841
for result in self._iter_specific_file_parents():
3844
def _iter_specific_file_parents(self):
3845
"""Iter over the specific file parents."""
3846
while self.search_specific_file_parents:
3847
# Process the parent directories for the paths we were iterating.
3848
# Even in extremely large trees this should be modest, so currently
3849
# no attempt is made to optimise.
3850
path_utf8 = self.search_specific_file_parents.pop()
3851
if osutils.is_inside_any(self.searched_specific_files, path_utf8):
3852
# We've examined this path.
3854
if path_utf8 in self.searched_exact_paths:
3855
# We've examined this path.
3857
path_entries = self.state._entries_for_path(path_utf8)
3858
# We need either one or two entries. If the path in
3859
# self.target_index has moved (so the entry in source_index is in
3860
# 'ar') then we need to also look for the entry for this path in
3861
# self.source_index, to output the appropriate delete-or-rename.
3862
selected_entries = []
3864
for candidate_entry in path_entries:
3865
# Find entries present in target at this path:
3866
if candidate_entry[1][self.target_index][0] not in 'ar':
3868
selected_entries.append(candidate_entry)
3869
# Find entries present in source at this path:
3870
elif (self.source_index is not None and
3871
candidate_entry[1][self.source_index][0] not in 'ar'):
3873
if candidate_entry[1][self.target_index][0] == 'a':
3874
# Deleted, emit it here.
3875
selected_entries.append(candidate_entry)
3877
# renamed, emit it when we process the directory it
3879
self.search_specific_file_parents.add(
3880
candidate_entry[1][self.target_index][1])
3882
raise AssertionError(
3883
"Missing entry for specific path parent %r, %r" % (
3884
path_utf8, path_entries))
3885
path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
3886
for entry in selected_entries:
3887
if entry[0][2] in self.seen_ids:
3889
result, changed = self._process_entry(entry, path_info)
3891
raise AssertionError(
3892
"Got entry<->path mismatch for specific path "
3893
"%r entry %r path_info %r " % (
3894
path_utf8, entry, path_info))
3895
# Only include changes - we're outside the users requested
3898
self._gather_result_for_consistency(result)
3899
if (result[6][0] == 'directory' and
3900
result[6][1] != 'directory'):
3901
# This stopped being a directory, the old children have
3903
if entry[1][self.source_index][0] == 'r':
3904
# renamed, take the source path
3905
entry_path_utf8 = entry[1][self.source_index][1]
3907
entry_path_utf8 = path_utf8
3908
initial_key = (entry_path_utf8, '', '')
3909
block_index, _ = self.state._find_block_index_from_key(
3911
if block_index == 0:
3912
# The children of the root are in block index 1.
3914
current_block = None
3915
if block_index < len(self.state._dirblocks):
3916
current_block = self.state._dirblocks[block_index]
3917
if not osutils.is_inside(
3918
entry_path_utf8, current_block[0]):
3919
# No entries for this directory at all.
3920
current_block = None
3921
if current_block is not None:
3922
for entry in current_block[1]:
3923
if entry[1][self.source_index][0] in 'ar':
3924
# Not in the source tree, so doesn't have to be
3927
# Path of the entry itself.
3929
self.search_specific_file_parents.add(
3930
osutils.pathjoin(*entry[0][:2]))
3931
if changed or self.include_unchanged:
3933
self.searched_exact_paths.add(path_utf8)
3935
def _path_info(self, utf8_path, unicode_path):
3936
"""Generate path_info for unicode_path.
3938
:return: None if unicode_path does not exist, or a path_info tuple.
3940
abspath = self.tree.abspath(unicode_path)
3942
stat = os.lstat(abspath)
3944
if e.errno == errno.ENOENT:
3945
# the path does not exist.
3949
utf8_basename = utf8_path.rsplit('/', 1)[-1]
3950
dir_info = (utf8_path, utf8_basename,
3951
osutils.file_kind_from_stat_mode(stat.st_mode), stat,
3953
if dir_info[2] == 'directory':
3954
if self.tree._directory_is_tree_reference(
3956
self.root_dir_info = self.root_dir_info[:2] + \
3957
('tree-reference',) + self.root_dir_info[3:]
3800
3961
# Try to load the compiled form if possible